├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .github ├── codechecker │ ├── config.json │ └── skipfile.txt ├── scripts │ └── get-dependencies.sh └── workflows │ ├── build.yml │ └── codechecker.yml ├── .gitignore ├── CMakeLists.txt ├── Doxyfile.in ├── LICENSE ├── README.md ├── cmake ├── MonomuxCPack.cmake ├── MonomuxConfig.cmake ├── MonomuxDoxygen.cmake ├── MonomuxVersion.cmake └── WriteVersion.in.cmake ├── docs └── README.md ├── include ├── CMakeLists.txt ├── core │ ├── CMakeLists.txt │ └── monomux │ │ ├── Debug.h │ │ ├── Log.hpp │ │ ├── Version.hpp │ │ ├── adt │ │ ├── Atomic.hpp │ │ ├── Lazy.hpp │ │ ├── MemberFunctionHelper.hpp │ │ ├── POD.hpp │ │ ├── RingBuffer.hpp │ │ ├── ScopeGuard.hpp │ │ ├── SmallIndexMap.hpp │ │ ├── Tagged.hpp │ │ └── UniqueScalar.hpp │ │ ├── client │ │ ├── Client.hpp │ │ ├── ControlClient.hpp │ │ ├── Dispatch.ipp │ │ └── SessionData.hpp │ │ ├── control │ │ ├── Message.hpp │ │ ├── MessageBase.hpp │ │ └── PascalString.hpp │ │ ├── server │ │ ├── ClientData.hpp │ │ ├── Dispatch.ipp │ │ ├── Server.hpp │ │ └── SessionData.hpp │ │ ├── system │ │ ├── BufferedChannel.hpp │ │ ├── Channel.hpp │ │ ├── CheckedPOSIX.hpp │ │ ├── Environment.hpp │ │ ├── Event.hpp │ │ ├── Pipe.hpp │ │ ├── Process.hpp │ │ ├── Pty.hpp │ │ ├── Socket.hpp │ │ ├── Time.hpp │ │ └── fd.hpp │ │ └── unreachable.hpp └── implementation │ ├── CMakeLists.txt │ └── monomux │ ├── client │ ├── Main.hpp │ └── Terminal.hpp │ ├── server │ └── Main.hpp │ └── system │ ├── Crash.hpp │ └── Signal.hpp ├── src ├── CMakeLists.txt ├── Config.cpp ├── Config.hpp ├── Config.in.h ├── ExitCode.hpp ├── Log.cpp ├── Version.cpp ├── Version.in.h ├── client │ ├── CMakeLists.txt │ ├── Client.cpp │ ├── ControlClient.cpp │ ├── Dispatch.cpp │ ├── Main.cpp │ └── Terminal.cpp ├── control │ ├── CMakeLists.txt │ └── Message.cpp ├── main.cpp ├── server │ ├── CMakeLists.txt │ ├── ClientData.cpp │ ├── Dispatch.cpp │ ├── Main.cpp │ ├── Server.cpp │ └── SessionData.cpp ├── system │ ├── BufferedChannel.cpp │ ├── CMakeLists.txt │ ├── Channel.cpp │ ├── Crash.cpp │ ├── Environment.cpp │ ├── Event.cpp │ ├── Pipe.cpp │ ├── Process.cpp │ ├── Pty.cpp │ ├── Signal.cpp │ ├── Socket.cpp │ └── fd.cpp └── unreachable.cpp └── test ├── CMakeLists.txt ├── adt ├── RingBufferTest.cpp └── SmallIndexMapTest.cpp ├── control └── MessageSerialisationTest.cpp └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveMacros: false 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllArgumentsOnNextLine: true 12 | AllowAllConstructorInitializersOnNextLine: true 13 | AllowAllParametersOfDeclarationOnNextLine: true 14 | AllowShortBlocksOnASingleLine: true 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: All 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: MultiLine 24 | BinPackArguments: false 25 | BinPackParameters: false 26 | BraceWrapping: 27 | AfterCaseLabel: true 28 | AfterClass: true 29 | AfterControlStatement: true 30 | AfterEnum: true 31 | AfterFunction: true 32 | AfterNamespace: true 33 | AfterStruct: true 34 | AfterUnion: true 35 | AfterExternBlock: true 36 | BeforeCatch: true 37 | BeforeElse: true 38 | IndentBraces: false 39 | SplitEmptyFunction: false 40 | SplitEmptyRecord: false 41 | SplitEmptyNamespace: false 42 | BreakBeforeBinaryOperators: None 43 | BreakBeforeBraces: Custom 44 | BreakBeforeInheritanceComma: true 45 | BreakInheritanceList: BeforeColon 46 | BreakBeforeTernaryOperators: true 47 | BreakConstructorInitializersBeforeComma: false 48 | BreakConstructorInitializers: BeforeColon 49 | BreakStringLiterals: true 50 | ColumnLimit: 80 51 | CommentPragmas: '^ IWYU pragma:' 52 | CompactNamespaces: false 53 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 54 | ConstructorInitializerIndentWidth: 2 55 | ContinuationIndentWidth: 2 56 | Cpp11BracedListStyle: true 57 | DerivePointerAlignment: true 58 | DisableFormat: false 59 | ExperimentalAutoDetectBinPacking: false 60 | FixNamespaceComments: true 61 | ForEachMacros: 62 | - foreach 63 | - Q_FOREACH 64 | - BOOST_FOREACH 65 | IncludeBlocks: Preserve 66 | IncludeCategories: 67 | - Regex: '^"(monomux)/' 68 | Priority: 2 69 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 70 | Priority: 3 71 | - Regex: '.*' 72 | Priority: 1 73 | IncludeIsMainRegex: '(Test)?$' 74 | IndentCaseLabels: true 75 | IndentPPDirectives: None 76 | IndentWidth: 2 77 | IndentWrappedFunctionNames: false 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 2 82 | NamespaceIndentation: None 83 | PenaltyBreakAssignment: 2 84 | PenaltyBreakBeforeFirstCallParameter: 19 85 | PenaltyBreakComment: 300 86 | PenaltyBreakFirstLessLess: 120 87 | PenaltyBreakString: 1000 88 | PenaltyBreakTemplateDeclaration: 10 89 | PenaltyExcessCharacter: 1000000 90 | PenaltyReturnTypeOnItsOwnLine: 60 91 | PointerAlignment: Left 92 | ReflowComments: true 93 | SortIncludes: true 94 | SortUsingDeclarations: true 95 | SpaceAfterCStyleCast: false 96 | SpaceAfterLogicalNot: false 97 | SpaceAfterTemplateKeyword: true 98 | SpaceBeforeAssignmentOperators: true 99 | SpaceBeforeCpp11BracedList: false 100 | SpaceBeforeCtorInitializerColon: true 101 | SpaceBeforeInheritanceColon: true 102 | SpaceBeforeParens: ControlStatements 103 | SpaceBeforeRangeBasedForLoopColon: true 104 | SpaceInEmptyParentheses: false 105 | SpacesBeforeTrailingComments: 1 106 | SpacesInAngles: false 107 | SpacesInContainerLiterals: true 108 | SpacesInCStyleCastParentheses: false 109 | SpacesInParentheses: false 110 | SpacesInSquareBrackets: false 111 | Standard: c++17 112 | StatementMacros: 113 | - Q_UNUSED 114 | - QT_REQUIRE_VERSION 115 | TabWidth: 8 116 | UseTab: Never 117 | ... 118 | 119 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: >- 2 | -*, 3 | clang-diagnosic-*, 4 | bugprone-*, 5 | bugprone-easily-swappable-parameters, 6 | -bugprone-narrowing-conversions, 7 | -bugprone-unchecked-optional-access, 8 | cert-*, 9 | google-build-namespaces, 10 | llvm-header-guard, 11 | llvm-include-order, 12 | llvm-namespace-comment, 13 | misc-*, 14 | -misc-non-private-member-variables-in-classes, 15 | -misc-unused-parameters, 16 | modernize-*, 17 | -modernize-avoid-c-arrays, 18 | -modernize-concat-nested-namespaces, 19 | -modernize-use-nodiscard, 20 | -modernize-use-trailing-return-type, 21 | performance-*, 22 | readability-*, 23 | -readability-braces-around-statements, 24 | -readability-convert-member-functions-to-static, 25 | -readability-function-cognitive-complexity, 26 | -readability-implicit-bool-conversion, 27 | readability-identifier-naming, 28 | -readability-identifier-length, 29 | readability-suspicious-call-argument 30 | CheckOptions: 31 | - key: bugprone-easily-swappable-parameters.QualifiersMix 32 | value: true 33 | - key: bugprone-easily-swappable-parameters.ModelImplicitConversions 34 | value: true 35 | - key: performance-move-const-arg.CheckTriviallyCopyableMove 36 | value: false 37 | - key: readability-identifier-naming.ClassCase 38 | value: CamelCase 39 | - key: readability-identifier-naming.EnumCase 40 | value: CamelCase 41 | - key: readability-identifier-naming.FunctionCase 42 | value: camelBack 43 | - key: readability-identifier-naming.MemberCase 44 | value: CamelCase 45 | - key: readability-identifier-naming.ParameterCase 46 | value: CamelCase 47 | - key: readability-identifier-naming.UnionCase 48 | value: CamelCase 49 | - key: readability-identifier-naming.VariableCase 50 | value: CamelCase 51 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | # Make sure there's a newline at the end of the files. 9 | insert_final_newline = true 10 | 11 | 12 | [*.{md,rst}] 13 | trim_trailing_whitespace = false 14 | 15 | 16 | [*.{c,cc,cpp,cxx,h,hh,hpp,hxx,i,ii,ipp,ixx,icc,inc}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | 21 | [*.td] 22 | indent_style = space 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /.github/codechecker/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "analyze": [ 3 | "--enable=default", 4 | "--enable=portability", 5 | "--enable=security", 6 | "--enable=sensitive", 7 | "--analyzer-config=clang-tidy:take-config-from-directory=true", 8 | "--skip=.github/codechecker/skipfile.txt" 9 | ], 10 | "parse": [ 11 | "--skip=.github/codechecker/skipfile.txt" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.github/codechecker/skipfile.txt: -------------------------------------------------------------------------------- 1 | -/usr/include/* 2 | -/usr/lib/llvm*/lib/clang/*/include/* 3 | -*/Build/_deps/* 4 | -------------------------------------------------------------------------------- /.github/scripts/get-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | echo "::group::Updating package lists" 5 | # Remove this so we get to test with the compiler that is **OFFICIALLY** 6 | # available. 7 | # sudo apt-get install -y software-properties-common # Supplies add-apt-repository. 8 | # sudo add-apt-repository --remove -y "ppa:ubuntu-toolchain-r/test" 9 | # sudo apt-get -y clean 10 | # sudo rm -rf /var/lib/apt/lists/* 11 | sudo apt-get -y update 12 | echo "::endgroup::" 13 | 14 | echo "::group::Installing compiler $COMPILER" 15 | # Get the latest version of the compiler available for the distribution. 16 | # CC=$(apt-cache search $(echo "$COMPILER") \ 17 | # | grep "^${COMPILER}-" \ 18 | # | cut -d "-" -f 1-2 \ 19 | # | grep "\-[0-9]" \ 20 | # | sort -rV \ 21 | # | head -n 1 \ 22 | # ) 23 | # 24 | # Unfortunately, GitHub Actions is a bit ridiculous, and they seem to not let 25 | # you *PROPERLY* clear the package list and fetch the package that is available 26 | # as the latest version because they use a lot of custom package sources... 27 | # 28 | # So I guess we're just hardcoding these for now... 29 | if [[ "${COMPILER}" == "gcc" ]] 30 | then 31 | if [[ "${DISTRO}" == "ubuntu-18.04" ]] 32 | then 33 | CC="gcc-8" 34 | elif [[ "${DISTRO}" == "ubuntu-20.04" ]] 35 | then 36 | CC="gcc-10" 37 | elif [[ "${DISTRO}" == "ubuntu-22.04" ]] 38 | then 39 | CC="gcc-12" 40 | fi 41 | 42 | CXX=$(echo ${CC} | sed 's/gcc/g++/') 43 | # With GCC, you need to install G++-X to get C++ compilation. 44 | sudo apt-get -y install ${CXX} 45 | elif [[ "${COMPILER}" == "clang" ]]; 46 | then 47 | if [[ "${DISTRO}" == "ubuntu-18.04" ]] 48 | then 49 | CC="clang-10" 50 | elif [[ "${DISTRO}" == "ubuntu-20.04" ]] 51 | then 52 | CC="clang-12" 53 | elif [[ "${DISTRO}" == "ubuntu-22.04" ]] 54 | then 55 | CC="clang-14" 56 | fi 57 | 58 | CXX=$(echo ${CC} | sed 's/clang/clang++/') 59 | # Clang works both ways. 60 | sudo apt-get -y install ${CC} 61 | fi 62 | echo "Using compiler ${CC} (${CXX})..." 63 | echo "::set-output name=CC::${CC}" 64 | echo "::set-output name=CXX::${CXX}" 65 | echo "::endgroup::" 66 | 67 | echo "::group::Installing CMake" 68 | # These Ubuntu images of GitHub Actions are getting really annoying... so turns 69 | # out that they completely disrespect their users by having unwanted stuff 70 | # installed under /usr/**local** which gets priority over what the user would 71 | # like to **EXPLICITLY** install via the package manager... 72 | sudo rm -v $(which cmake) $(which cpack) 73 | 74 | if [[ "${DISTRO}" == "ubuntu-18.04" || "${DISTRO}" == "ubuntu-20.04" ]] 75 | then 76 | sudo apt-get install -y \ 77 | lsb-release \ 78 | software-properties-common \ 79 | wget 80 | wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc \ 81 | 2>/dev/null \ 82 | | gpg --dearmor - \ 83 | | sudo tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null 84 | sudo apt-add-repository -y \ 85 | "deb https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" 86 | sudo apt-get -y update 87 | fi 88 | 89 | if [[ "${DISTRO}" == "ubuntu-18.04" ]] 90 | then 91 | # We need a newer version of CMake than supplied by Ubuntu... 92 | # * FetchContent is only available 3.11 and onwards. 93 | # * Unity build is only available in 3.16 and onwards. 94 | # Ubuntu 18 gives only 3.10. 95 | sudo apt-get -y install \ 96 | cmake="3.16.5-0kitware1" \ 97 | cmake-data="3.16.5-0kitware1" 98 | elif [[ "${DISTRO}" == "ubuntu-20.04" ]] 99 | then 100 | # The lowest version available on the Kitware PPA for Ubuntu 20.04. 101 | sudo apt-get -y install \ 102 | cmake="3.17.3-0kitware1ubuntu20.04.1" \ 103 | cmake-data="3.17.3-0kitware1ubuntu20.04.1" 104 | elif [[ "${DISTRO}" == "ubuntu-22.04" ]] 105 | then 106 | # The lowest version available for Ubuntu 22.04. 107 | # At the time of writing, the PPA did not contain an entry for this 108 | # distribution. 109 | sudo apt-get -y install \ 110 | cmake="3.22.1-1ubuntu1" \ 111 | cmake-data="3.22.1-1ubuntu1" 112 | fi 113 | echo "::endgroup::" 114 | -------------------------------------------------------------------------------- /.github/workflows/codechecker.yml: -------------------------------------------------------------------------------- 1 | name: CodeChecker C++ Static Analysis 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.c' 7 | - '**.cpp' 8 | - '**.h' 9 | - '**.hpp' 10 | - '**.i' 11 | - '**.ipp' 12 | - '.github/codechecker/**' 13 | - '.github/workflows/codechecker.yml' 14 | - '.clang-tidy' 15 | branches: 16 | - '**' 17 | tags: 18 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 19 | pull_request: 20 | 21 | # Attempt to cancel any in-progress jobs for a given PR. 22 | concurrency: 23 | group: codechecker-${{ github.ref }} 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | ubuntu_2004: 28 | name: "Ubuntu Linux 20.04" 29 | runs-on: ubuntu-20.04 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: "Install dependencies" 33 | id: dependencies 34 | env: 35 | COMPILER: 'clang' 36 | DISTRO: 'ubuntu-20.04' 37 | shell: bash 38 | run: .github/scripts/get-dependencies.sh 39 | - name: "Prepare build with CMake" 40 | env: 41 | CC: ${{ steps.dependencies.outputs.CC }} 42 | CXX: ${{ steps.dependencies.outputs.CXX }} 43 | run: | 44 | cmake --version 45 | 46 | cmake -S . -B Build \ 47 | -DCMAKE_BUILD_TYPE=Debug \ 48 | -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ 49 | -DMONOMUX_NON_ESSENTIAL_LOGS=ON \ 50 | -DMONOMUX_BUILD_TESTS=ON 51 | 52 | - name: "Execute Static Analysis" 53 | uses: whisperity/codechecker-analysis-action@v1 54 | id: codechecker 55 | continue-on-error: true 56 | with: 57 | llvm-version: 'latest' 58 | 59 | config: .github/codechecker/config.json 60 | 61 | build-command: "cd Build && cmake --build . -- -j3" 62 | 63 | # ctu: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }} 64 | 65 | # Don't do differential check for now. 66 | # diff: false 67 | # diff-url: ${{ secrets.CODECHECKER_URL }} 68 | # diff-username: ${{ secrets.CODECHECKER_USERNAME }} 69 | # diff-password: ${{ secrets.CODECHECKER_PASSWORD }} 70 | 71 | store: true 72 | store-url: ${{ secrets.CODECHECKER_URL }} 73 | store-username: ${{ secrets.CODECHECKER_USERNAME }} 74 | store-password: ${{ secrets.CODECHECKER_PASSWORD }} 75 | 76 | - name: "Generate version info for artefact" 77 | id: version 78 | run: | 79 | set -x 80 | echo "::set-output name=PACKAGE_VERSION::$(cat Build/Version.txt)" 81 | 82 | - name: "Upload HTML reports" 83 | uses: actions/upload-artifact@v2 84 | with: 85 | name: "monomux-${{ steps.version.outputs.PACKAGE_VERSION }}-codechecker-${{ steps.codechecker.outputs.codechecker-version }}+${{ steps.codechecker.outputs.codechecker-hash }}-llvm-${{ steps.codechecker.outputs.llvm-version }}-results" 86 | path: "${{ steps.codechecker.outputs.result-html-dir }}" 87 | if-no-files-found: error 88 | - name: "Upload analysis failure reproducers" 89 | uses: actions/upload-artifact@v2 90 | with: 91 | name: "monomux-${{ steps.version.outputs.PACKAGE_VERSION }}-codechecker-${{ steps.codechecker.outputs.codechecker-version }}+${{ steps.codechecker.outputs.codechecker-hash }}-llvm-${{ steps.codechecker.outputs.llvm-version }}-failures" 92 | path: "${{ steps.codechecker.outputs.analyze-output }}/failed" 93 | if-no-files-found: ignore 94 | 95 | - name: "Finish job" 96 | run: | 97 | echo "::notice title=Static analysis job set to always pass::The job is configured in a way that it always passes. Please see the reports in the generated and uploaded artefacts." 98 | exit 0 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # CMake build. 35 | [bB]uild/ 36 | [bB]uild-* 37 | [rR]elease/ 38 | [rR]elease-* 39 | 40 | # Clangd 41 | compile_commands.json 42 | /.cache 43 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15.0) 2 | 3 | # Just simply do not let the user do this, because this litters the project 4 | # tree with weird contents that are not verbosely elaborated in a .gitignore... 5 | set(CMAKE_DISABLE_SOURCE_CHANGES ON) 6 | set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) 7 | if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 8 | file(REMOVE "CMakeCache.txt") 9 | file(REMOVE_RECURSE "CMakeFiles") 10 | 11 | message(FATAL_ERROR "Raw in-tree builds are not supported, please use a dedicated 'Build' directory!" 12 | "\nDue to technical detail, 'CMakeCache.txt' and 'CMakeFiles/' will still be created as trash in your directory now, and you should remove them!") 13 | endif() 14 | 15 | list(APPEND CMAKE_MODULE_PATH 16 | "${CMAKE_SOURCE_DIR}/cmake" 17 | ) 18 | include(MonomuxVersion) 19 | 20 | project(monomux 21 | LANGUAGES CXX 22 | VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK} 23 | DESCRIPTION "MonoMux: Monophone Terminal Multiplexer" 24 | HOMEPAGE_URL "http://github.com/whisperity/MonoMux") 25 | 26 | if (NOT UNIX) 27 | message(FATAL_ERROR "This project supports only POSIX Unix/Linux platforms!") 28 | endif() 29 | 30 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 31 | if (NOT CMAKE_BUILD_TYPE) 32 | set(CMAKE_BUILD_TYPE Debug) 33 | endif() 34 | 35 | include(GNUInstallDirs) 36 | include(MonomuxConfig) 37 | 38 | set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "Monomux") 39 | 40 | # Support adding compiler diagnostic flags dynamically, based on whether the 41 | # compiler supports them. 42 | include(CheckCXXCompilerFlag) 43 | function(check_add_compile_option OPT) 44 | # Create an output variable for check_cxx_compiler_flag(). 45 | string(REGEX REPLACE "^[-/]" "" var ${OPT}) 46 | string(REGEX REPLACE ":" "" var ${var}) 47 | check_cxx_compiler_flag(${OPT} ${var}) 48 | if (${var}) 49 | add_compile_options(${OPT}) 50 | else() 51 | message(STATUS "Skip unsupported ${OPT} with the current compiler.") 52 | endif() 53 | endfunction() 54 | 55 | set(CMAKE_CXX_EXTENSIONS OFF) 56 | set(CMAKE_CXX_STANDARD 17) 57 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 58 | 59 | if ( (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") ) 60 | check_add_compile_option(-Wall) 61 | check_add_compile_option(-Wextra) 62 | endif() 63 | 64 | message(STATUS "- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - ") 65 | message(STATUS " MonoMux (v${MONOMUX_VERSION_STRING})") 66 | message(STATUS "------------------------------------------------------------------------------") 67 | message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") 68 | message(STATUS "System: ${CMAKE_SYSTEM_PROCESSOR} (${CMAKE_SYSTEM_NAME})") 69 | message(STATUS "C++ standard: C++${CMAKE_CXX_STANDARD}") 70 | message(STATUS "Library type: ${MONOMUX_LIBRARY_TYPE}") 71 | message(STATUS "Non-essential log output: ${MONOMUX_NON_ESSENTIAL_LOGS}") 72 | message(STATUS "- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - ") 73 | 74 | # TODO: Add -UNDEBUG so #ifndef NDEBUG and asserts are there for RelWithDebInfo. 75 | 76 | include_directories( 77 | "${CMAKE_SOURCE_DIR}/include/core" 78 | "${CMAKE_SOURCE_DIR}/include/implementation" 79 | "${CMAKE_BINARY_DIR}/include" 80 | ) 81 | 82 | add_subdirectory(include) 83 | add_subdirectory(src) 84 | 85 | include(MonomuxDoxygen) 86 | include(MonomuxCPack) 87 | 88 | add_subdirectory(test) 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MonoMux 2 | ======= 3 | 4 | MonoMux (for _Monophone Terminal Multiplexer_ — pun intended) is a system tool that allows executing terminal sessions in the background with on-demand attaching to them. 5 | 6 | > **📢 Important!** MonoMux is in active incremental development! 7 | > You are welcome using it as your daily driver, but for long-term production systems, use the proven alternatives instead! 8 | 9 | MonoMux is a tool similar to [`screen`](http://gnu.org/software/screen/) and [`tmux`](https://github.com/tmux/tmux/wiki). 10 | It allows most of the core features of _screen_ or _tmux_, with being less intrusive about its behaviour when it comes to using these tools with modern _terminal emulators_. 11 | 12 | > **⚠️ Warning!** Currently, _MonoMux_ is designed with only supporting Linux operating systems in mind. 13 | > Most of the project is written with POSIX system calls in mind, but there are *some* GNU extensions used. 14 | 15 | > **ℹ️ Note:** _MonoMux_ is **NOT** a terminal emulator by itself! 16 | > To use it, you may use any of your favourite terminal emulators. 17 | 18 | Dependencies 19 | ------------ 20 | 21 | MonoMux uses modern C++ features, and as such, a C++17-capable compiler and associated standard library is needed to compile and execute the tool. 22 | There are no other dependencies. 23 | 24 | Installation 25 | ------------ 26 | 27 | ### Ubuntu (18.04, 20.04, 22.04) 28 | 29 | Download the `.deb` (and optionally the `.ddeb`) file for the release, and install the standard way: 30 | 31 | ~~~{.bash} 32 | sudo dpkg --install monomux-*.*deb 33 | ~~~ 34 | 35 | Alternatively, a `.tar.gz` archive might be used. 36 | The application consists of a single self-contained binary. 37 | 38 | Usage 39 | ----- 40 | 41 | The easiest use of MonoMux is simply starting it: `monomux`. 42 | By default, a server starts in the background, and a default session is created with the default shell of the current user, and the client automatically attaches to this session. 43 | Executing the client with a server already running will attach to the only session on the server, or if multiple sessions exist, an interactive menu will start with which a session can be selected. 44 | (The interactive menu can be explicitly requested, even if only at most one session exists, with the `-i` or `--interactive` parameter.) 45 | 46 | > **ℹ️ Note:** Please always refer to the output of `monomux -h` for up-to-date information about what flags the installed tool supports. 47 | 48 | To run multiple independent servers, specify the `-s`/`--socket` option with a path on the file system. 49 | Communication between the server and the client takes place on this socket. 50 | 51 | ### Fine-tuning Client options 52 | 53 | The client can be fine-tuned during its start-up with several flags: 54 | 55 | * `monomux /bin/myshell` will start the specified program without passing any arguments to it. 56 | * `monomux -- /bin/myshell -a -b --arg-to-shell` will start the specified program with command-line arguments, if a new session is created. 57 | (If the started program takes `-` or `--` arguments, an _explicit_ separator `--` must be given **BEFORE** the program's name!) 58 | * `monomux -n SESSION_NAME` will attach or start the session `SESSION_NAME`, bypassing the interactive menu. 59 | * Environment variables can be specified or removed via `-e VARIABLE=Value -u UNSET_VARIABLE`. 60 | 61 | A server can be started explicitly via `monomux --server`, in which case no client creation and attachment will be done. 62 | 63 | Why? 64 | ---- 65 | 66 | One of the most important contexts where _screen_ or _tmux_ comes to mind is over remote sessions. 67 | If a remote connection breaks — or the local graphical terminal closes or crashes —, the terminal session behind the connection is sent a **`SIGHUP`** signal, for which most programs exit. 68 | This results in the loss of shell history, and the interrupt of running programs. 69 | 70 | The most crucial problem from an interactive work's point-of-view with existing tools is that both _screen_ and _tmux_ **act as terminal emulators** themselves. 71 | Their behaviour is to _parse_ the [VT sequences](http://vt100.net/docs/vt100-ug/chapter3.html) of the output received from the "remote" terminal and emit them to the attached client(s). 72 | Programs using extensive modern, or terminal specific features will have to fall back to the older and more restrictive set of what _screen_ or _tmux_ understands. 73 | 74 | A fork of _screen_, [`dtach`](http://github.com/crigler/dtach) was created which emulates **only** the attach/detach features of _screen_. 75 | However, _dtach_ has a straightforward and non-trivial interface, e.g. the user must specify the connection socket file manually. 76 | 77 | (Moreover, all of the aforementioned tools are written in C.) 78 | 79 | MonoMux aims to combine the good aspects of all of these tools but remove almost all of the possible hurdles in the way of tools running in the background session. 80 | 81 | * Attach/detach features are supported without the need of parsing control sequences. 82 | * Every I/O operation to and from the client to the attached session is passed **verbatim**, without understanding the contents. 83 | * This allows using all the features of a modern _terminal emulator_ as-is. 84 | * However, this also means that features found in _tmux_ such as splits or keybinds _can not_ and **will not** be implemented in this tool. 85 | * Better defaults than _dtach_: no need to specify an exit escape sequence or modern resize events. 86 | * Like _tmux_, there is meaningful session management by default, giving an interactive attach menu if multiple sessions exist. 87 | 88 | Written in C++17, with object-oriented design in mind. 89 | This might result in a larger binary than for other tools, however, _MonoMux_ is intended for user systems, not embedded contexts. 90 | -------------------------------------------------------------------------------- /cmake/MonomuxCPack.cmake: -------------------------------------------------------------------------------- 1 | set(CPACK_PACKAGE_NAME "MonoMux") 2 | set(CPACK_PACKAGE_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}") 3 | set(CPACK_PACKAGE_VENDOR "Whisperity") 4 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MonoMux: Monophone Terminal Multiplexer - Less intrusive than tmux, smarter than screen") 5 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md") 6 | set(CPACK_PACKAGE_CONTACT "Whisperity ") 7 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") 8 | set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") 9 | set(CPACK_PACKAGE_EXECUTABLES monomux "MonoMux") 10 | set(CPACK_CREATE_DESKTOP_LINKS monomux) 11 | 12 | set(CPACK_BINARY_TZ OFF) 13 | set(CPACK_BINARY_STGZ OFF) 14 | set(CPACK_ARCHIVE_PACKAGE_DEBUG ON) 15 | # Create one TGZ for each component. 16 | set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) 17 | 18 | if (UNIX) 19 | set(CPACK_BINARY_DEB ON) 20 | # set(CPACK_DEBIAN_PACKAGE_DEBUG ON) 21 | 22 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.25), libstdc++6 (>= 5.2), libgcc1 (>= 1:8)") 23 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/whisperity/MonoMux") 24 | 25 | # Create one Debian package for each (top level) component. 26 | set(CPACK_DEB_COMPONENT_INSTALL ON) 27 | set(CPACK_DEBIAN_MONOMUXUSERGROUP_PACKAGE_NAME "monomux") 28 | 29 | set(CPACK_DEBIAN_MONOMUXDEVELOPERGROUP_PACKAGE_NAME "libmonomux-dev") 30 | set(CPACK_COMPONENT_MONOMUXDEVELOPERGROUP_DESCRIPTION "Contains the library code that allows reusing MonoMux inside another project. This package only contains both the core library and the user-facing reference implementation details.") 31 | set(CPACK_DEBIAN_MONOMUXDEVELOPERGROUP_DESCRIPTION "${CPACK_COMPONENT_MONOMUXDEVELOPERGROUP_DESCRIPTION}") 32 | 33 | set(CPACK_DEBIAN_MONOMUXUSERGROUP_PACKAGE_SECTION "admin") 34 | set(CPACK_DEBIAN_MONOMUXDEVELOPERGROUP_PACKAGE_SECTION "devel") 35 | 36 | set(CPACK_DEBIAN_MONOMUXUSERGROUP_DEBUGINFO_PACKAGE ON) 37 | 38 | # Install some additional resources into the Unix package. 39 | set(EXTRA_FILES_TO_INSTALL 40 | "${CMAKE_SOURCE_DIR}/README.md" 41 | "${CMAKE_SOURCE_DIR}/LICENSE" 42 | ) 43 | install(FILES ${EXTRA_FILES_TO_INSTALL} 44 | DESTINATION "${CMAKE_INSTALL_DATADIR}/monomux" 45 | COMPONENT "${MONOMUX_NAME}" 46 | ) 47 | install(FILES ${EXTRA_FILES_TO_INSTALL} 48 | DESTINATION "${CMAKE_INSTALL_DATADIR}/libmonomux-dev" 49 | COMPONENT "${MONOMUX_CORE_LIBRARY_DEV_NAME}" 50 | ) 51 | install(FILES ${EXTRA_FILES_TO_INSTALL} 52 | DESTINATION "${CMAKE_INSTALL_DATADIR}/libmonomux-dev" 53 | COMPONENT "${MONOMUX_IMPLEMENTATION_LIBRARY_DEV_NAME}" 54 | ) 55 | endif() 56 | 57 | # Run CPack. It needs the variables above to be set up correctly before include. 58 | include(CPack) 59 | 60 | # But the cpack_add_component() function comes from the CPack.cmake, so this 61 | # has to come after. 62 | 63 | cpack_add_component_group(MonomuxUserGroup 64 | DISPLAY_NAME "MonoMux application" 65 | DESCRIPTION "Contains the MonoMux client for everyday use." 66 | BOLD_TITLE 67 | ) 68 | cpack_add_install_type(MonomuxUserType 69 | DISPLAY_NAME "Client" 70 | ) 71 | 72 | cpack_add_component_group(MonomuxDeveloperGroup 73 | DISPLAY_NAME "MonoMux reusable library" 74 | DESCRIPTION "Contains the MonoMux reusable ${MONOMUX_LIBRARY_TYPE} libraries. This is intended for developers only." 75 | ) 76 | cpack_add_install_type(MonomuxDeveloperType 77 | DISPLAY_NAME "Library" 78 | ) 79 | 80 | cpack_add_component(${MONOMUX_NAME} 81 | DISPLAY_NAME "MonoMux" 82 | REQUIRED 83 | GROUP MonomuxUserGroup 84 | INSTALL_TYPES 85 | MonomuxUserType 86 | MonomuxDeveloperType 87 | ) 88 | 89 | cpack_add_component(${MONOMUX_CORE_LIBRARY_DEV_NAME} 90 | DISPLAY_NAME "MonoMux Core - Development package" 91 | DESCRIPTION "Contains the library code that allows reusing MonoMux inside another project. This package only contains the library, without the user-facing reference implementation." 92 | GROUP MonomuxDeveloperGroup 93 | INSTALL_TYPES 94 | MonomuxDeveloperType 95 | DISABLED 96 | ) 97 | 98 | cpack_add_component(${MONOMUX_IMPLEMENTATION_LIBRARY_DEV_NAME} 99 | DISPLAY_NAME "MonoMux Implementation - Development package" 100 | DESCRIPTION "Contains the library code that allows reusing MonoMux inside another project. This package only contains the user-facing reference implementation details." 101 | DEPENDS 102 | "${MONOMUX_CORE_LIBRARY_DEV_NAME}" 103 | GROUP MonomuxDeveloperGroup 104 | INSTALL_TYPES 105 | MonomuxDeveloperType 106 | DISABLED 107 | ) 108 | -------------------------------------------------------------------------------- /cmake/MonomuxConfig.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(MONOMUX_CORE_LIBRARY_NAME "MonomuxCoreLibrary") 3 | set(MONOMUX_CORE_LIBRARY_DEV_NAME "MonomuxCoreLibraryDevelopoment") 4 | 5 | set(MONOMUX_IMPLEMENTATION_LIBRARY_NAME "MonomuxLibrary") 6 | set(MONOMUX_IMPLEMENTATION_LIBRARY_DEV_NAME "MonomuxLibraryDevelopment") 7 | 8 | set(MONOMUX_NAME "Monomux") 9 | set(MONOMUX_DEV_NAME "MonomuxDevelopment") 10 | 11 | 12 | set(MONOMUX_BUILD_SHARED_LIBS OFF CACHE BOOL 13 | "If set, the built binaries will be composed of several shared library (.so, .dll) for reusability in other projects. Turn off to improve optimisations if Monomux is only used as the reference implementation tool (normally it is)." 14 | ) 15 | if (NOT MONOMUX_BUILD_SHARED_LIBS) 16 | set(MONOMUX_LIBRARY_TYPE "STATIC") 17 | else() 18 | set(MONOMUX_LIBRARY_TYPE "SHARED") 19 | endif() 20 | 21 | set(MONOMUX_BUILD_UNITY OFF CACHE BOOL 22 | "If set, the built binary will be created from a SINGLE translation unit, which usually improves run-time performance a great deal, at the expence of significant drag on compiler performance.") 23 | if (MONOMUX_BUILD_UNITY) 24 | if (MONOMUX_LIBRARY_TYPE STREQUAL "SHARED") 25 | message(WARNING "Unity build with shared libraries does not make much sense. Prioritising unity build.") 26 | endif() 27 | 28 | set(MONOMUX_LIBRARY_TYPE "UNITY") 29 | set(CMAKE_UNITY_BUILD ON) 30 | set(CMAKE_UNITY_BUILD_BATCH_SIZE 0) 31 | else() 32 | unset(CMAKE_UNITY_BUILD CACHE) 33 | unset(CMAKE_UNITY_BUILD_BATCH_SIZE CACHE) 34 | endif() 35 | 36 | set(MONOMUX_NON_ESSENTIAL_LOGS ON CACHE BOOL 37 | "If set, the built binary will contain some additional log outputs that are needed for verbose debugging of the project. Turn off to cut down further on the binary size for production." 38 | ) 39 | 40 | configure_file(src/Config.in.h include/monomux/Config.h) 41 | install(FILES 42 | "${CMAKE_BINARY_DIR}/include/monomux/Config.h" 43 | "${CMAKE_BINARY_DIR}/include/monomux/Version.h" 44 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/monomux" 45 | COMPONENT "${MONOMUX_CORE_LIBRARY_DEV_NAME}" 46 | ) 47 | -------------------------------------------------------------------------------- /cmake/MonomuxDoxygen.cmake: -------------------------------------------------------------------------------- 1 | set(MONOMUX_BUILD_DOCS OFF CACHE BOOL 2 | "Whether to build documentation pages with Doxygen.") 3 | 4 | if (NOT MONOMUX_BUILD_DOCS) 5 | return() 6 | endif() 7 | 8 | find_package(Doxygen 9 | REQUIRED dot 10 | ) 11 | 12 | if (NOT DOXYGEN_FOUND) 13 | message(SEND_ERROR "Documentation generation was enabled, but Doxygen is not installed. Set MONOMUX_BUILD_DOCS to OFF to disable this warning.") 14 | return() 15 | endif() 16 | 17 | set(MONOMUX_DOCS_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src) 18 | set(MONOMUX_DOCS_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include) 19 | 20 | configure_file(Doxyfile.in Doxyfile) 21 | add_custom_target(docs 22 | COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 23 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 24 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 25 | COMMENT "Generating Doxygen documentation..." 26 | ) 27 | -------------------------------------------------------------------------------- /cmake/MonomuxVersion.cmake: -------------------------------------------------------------------------------- 1 | if (NOT DEFINED VERSION_HEADER_TEMPLATE OR NOT DEFINED VERSION_HEADER_RESULT) 2 | set(VERSION_HEADER_TEMPLATE "${CMAKE_SOURCE_DIR}/src/Version.in.h") 3 | set(VERSION_HEADER_RESULT "${CMAKE_BINARY_DIR}/include/monomux/Version.h") 4 | endif() 5 | if (NOT DEFINED VERSION_TXT_RESULT) 6 | set(VERSION_TXT_RESULT "${CMAKE_BINARY_DIR}/Version.txt") 7 | endif() 8 | 9 | # Emit the version information to the output. 10 | function(print_version) 11 | if (NOT VERSION_HAS_EXTRAS) 12 | message(STATUS "[Version] Result version: ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}") 13 | else() 14 | if (VERSION_OFFSET) 15 | message(STATUS "[Version] Offset from tagged version: ${VERSION_OFFSET}") 16 | endif() 17 | if (VERSION_COMMIT) 18 | set(VERSION_HAS_EXTRAS ON) 19 | message(STATUS "[Version] On commit: ${VERSION_COMMIT}") 20 | endif() 21 | if (VERSION_DIRTY) 22 | message(STATUS "[Version] Uncommitted local changes found!") 23 | endif() 24 | 25 | message(STATUS "[Version] Result version: ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}-${VERSION_OFFSET}-${VERSION_COMMIT}${VERSION_DIRTY}") 26 | endif() 27 | endfunction() 28 | 29 | # Emit the configuration of the Version header. 30 | function(write_version_header) 31 | configure_file("${VERSION_HEADER_TEMPLATE}" "${VERSION_HEADER_RESULT}") 32 | 33 | if (NOT VERSION_HAS_EXTRAS) 34 | file(WRITE "${VERSION_TXT_RESULT}" 35 | "${MONOMUX_VERSION_STRING}") 36 | else() 37 | file(WRITE "${VERSION_TXT_RESULT}" 38 | "${MONOMUX_VERSION_STRING}") 39 | endif() 40 | endfunction() 41 | 42 | execute_process(COMMAND git 43 | OUTPUT_VARIABLE git 44 | OUTPUT_STRIP_TRAILING_WHITESPACE) 45 | if (NOT git) 46 | message(WARNING "[Version] No Git command found, unable to extract version data.") 47 | set(VERSION_MAJOR 0) 48 | set(VERSION_MINOR 0) 49 | set(VERSION_PATCH 0) 50 | set(VERSION_TWEAK 0) 51 | set(VERSION_HAS_EXTAS OFF) 52 | 53 | print_version() 54 | write_version() 55 | return() 56 | endif() 57 | 58 | execute_process(COMMAND git describe --always --tags --dirty=-dirty 59 | OUTPUT_VARIABLE git_describe_result 60 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 61 | OUTPUT_STRIP_TRAILING_WHITESPACE) 62 | 63 | if (git_describe_result MATCHES "^v") 64 | string(REGEX MATCH 65 | "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9]*)-g([0-f]*))?(-dirty)?" 66 | _ 67 | "${git_describe_result}") 68 | set(VERSION_MAJOR "${CMAKE_MATCH_1}") 69 | set(VERSION_MINOR "${CMAKE_MATCH_2}") 70 | set(VERSION_PATCH "${CMAKE_MATCH_3}") 71 | set(VERSION_TWEAK 0) 72 | set(VERSION_HAS_EXTAS OFF) 73 | set(VERSION_OFFSET "${CMAKE_MATCH_5}") 74 | if (VERSION_OFFSET) 75 | set(VERSION_HAS_EXTRAS ON) 76 | else() 77 | set(VERSION_OFFSET 0) 78 | endif() 79 | set(VERSION_COMMIT "${CMAKE_MATCH_6}") 80 | if (VERSION_COMMIT) 81 | set(VERSION_HAS_EXTRAS ON) 82 | endif() 83 | if (CMAKE_MATCH_7 STREQUAL "-dirty") 84 | set(VERSION_HAS_EXTRAS ON) 85 | set(VERSION_DIRTY "-dirty") 86 | else() 87 | set(VERSION_DIRTY "") 88 | endif() 89 | else() 90 | string(REGEX MATCH "^([0-f]*)(-dirty)?" _ "${git_describe_result}") 91 | set(VERSION_MAJOR 0) 92 | set(VERSION_MINOR 0) 93 | set(VERSION_PATCH 0) 94 | set(VERSION_TWEAK 0) 95 | set(VERSION_HAS_EXTAS OFF) 96 | set(VERSION_OFFSET 0) 97 | set(VERSION_COMMIT "${CMAKE_MATCH_1}") 98 | if (VERSION_COMMIT) 99 | set(VERSION_HAS_EXTRAS ON) 100 | endif() 101 | if (CMAKE_MATCH_2 STREQUAL "-dirty") 102 | set(VERSION_HAS_EXTRAS ON) 103 | set(VERSION_DIRTY "-dirty") 104 | else() 105 | set(VERSION_DIRTY "") 106 | endif() 107 | endif() 108 | 109 | if (VERSION_COMMIT) 110 | execute_process(COMMAND git rev-parse ${VERSION_COMMIT} 111 | OUTPUT_VARIABLE git_full_hash 112 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 113 | OUTPUT_STRIP_TRAILING_WHITESPACE) 114 | set(VERSION_COMMIT "${git_full_hash}") 115 | endif() 116 | 117 | if (DEFINED ENV{GITHUB_RUN_NUMBER}) 118 | set(VERSION_TWEAK "$ENV{GITHUB_RUN_NUMBER}") 119 | message(STATUS "[Version] Currently executing build $ENV{GITHUB_RUN_NUMBER}") 120 | endif() 121 | 122 | if (NOT VERSION_HAS_EXTRAS) 123 | set(MONOMUX_VERSION_STRING 124 | "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}") 125 | else() 126 | set(MONOMUX_VERSION_STRING 127 | "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}-${VERSION_OFFSET}-${VERSION_COMMIT}${VERSION_DIRTY}") 128 | endif() 129 | 130 | # Generate a trampoline script from a template which will be executed to write 131 | # the version information into a generated header every time Git logs change. 132 | if (NOT VERSIONING_FROM_TRAMPOLINE) 133 | print_version() 134 | 135 | set(VERSION_GENERATOR_SCRIPT "${CMAKE_BINARY_DIR}/cmake/_Version.cmake") 136 | 137 | configure_file("${CMAKE_SOURCE_DIR}/cmake/WriteVersion.in.cmake" 138 | "${VERSION_GENERATOR_SCRIPT}") 139 | 140 | execute_process(COMMAND git rev-parse --absolute-git-dir 141 | OUTPUT_VARIABLE git_dir_path 142 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 143 | OUTPUT_STRIP_TRAILING_WHITESPACE) 144 | 145 | add_custom_command(OUTPUT "${VERSION_HEADER_RESULT}" 146 | COMMENT "Generating Version information" 147 | DEPENDS "${git_dir_path}/logs/HEAD" 148 | "${VERSION_GENERATOR_SCRIPT}" 149 | "${VERSION_HEADER_TEMPLATE}" 150 | COMMAND ${CMAKE_COMMAND} 151 | -P "${VERSION_GENERATOR_SCRIPT}") 152 | add_custom_target(monomux_generate_version_h 153 | DEPENDS "${VERSION_HEADER_RESULT}") 154 | 155 | set_source_files_properties("${VERSION_HEADER_RESULT}" 156 | PROPERTIES GENERATED TRUE 157 | HEADER_FILE_ONLY TRUE 158 | ) 159 | 160 | unset(VERSION_GENERATOR_SCRIPT) 161 | endif() 162 | -------------------------------------------------------------------------------- /cmake/WriteVersion.in.cmake: -------------------------------------------------------------------------------- 1 | # This trampoline script generates a C header containing version information. 2 | # This script is called automatically, see Version.cmake in the source tree. 3 | list(APPEND CMAKE_MODULE_PATH 4 | "${CMAKE_SOURCE_DIR}/cmake" 5 | ) 6 | 7 | set(VERSIONING_FROM_TRAMPOLINE ON) 8 | set(VERSION_HEADER_TEMPLATE "${VERSION_HEADER_TEMPLATE}") 9 | set(VERSION_HEADER_RESULT "${VERSION_HEADER_RESULT}") 10 | set(VERSION_TXT_RESULT "${VERSION_TXT_RESULT}") 11 | 12 | include(MonomuxVersion) 13 | 14 | write_version_header() 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | MonoMux 2 | ======= 3 | 4 | This auto-generated documentation describes the **internal architecture** and **components** that make up MonoMux's implementation. 5 | 6 | There are no instructions here on how to use MonoMux as a user. 7 | Please refer to the [homepage of the project](http://github.com/whisperity/MonoMux) for the main _README_. 8 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(core) 2 | add_subdirectory(implementation) 3 | -------------------------------------------------------------------------------- /include/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | install(DIRECTORY "monomux" 2 | TYPE INCLUDE 3 | COMPONENT "${MONOMUX_CORE_LIBRARY_DEV_NAME}") 4 | -------------------------------------------------------------------------------- /include/core/monomux/Debug.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | 21 | #define MONOMUX_DETAIL_CONDITIONALLY_TRUE(X) \ 22 | do \ 23 | { \ 24 | X; \ 25 | } while (false) 26 | #define MONOMUX_DETAIL_CONDITIONALLY_FALSE(X) ((void)0) 27 | 28 | 29 | #ifndef NDEBUG 30 | /* Convenience macro that is defined to its parameter if the program is 31 | * compiled in debug mode, and nothing otherwise, similarly to \p assert(). 32 | * 33 | * It has been turned \e ON in this build. 34 | */ 35 | #define MONOMUX_DEBUG(X) MONOMUX_DETAIL_CONDITIONALLY_TRUE(X) 36 | #else /* NDEBUG */ 37 | /* Convenience macro that is defined to its parameter if the program is 38 | * compiled in debug mode, and nothing otherwise, similarly to \p assert(). 39 | * 40 | * It has been turned \p OFF in this build. 41 | */ 42 | #define MONOMUX_DEBUG(X) MONOMUX_DETAIL_CONDITIONALLY_FALSE(X) 43 | #endif /* NDEBUG */ 44 | -------------------------------------------------------------------------------- /include/core/monomux/Log.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "monomux/Config.h" 26 | #include "monomux/Debug.h" 27 | 28 | namespace monomux::log 29 | { 30 | 31 | /// Severity levels for log messages. 32 | /// Severities linearly increase in "verbosity" level, so a lower severity 33 | /// \e number indicate a higher severity of the message. 34 | enum Severity 35 | { 36 | /// The highest severity level. Will always be printed, no matter what. 37 | None = 0, 38 | /// Critical messages that are likely the last printout from the system. 39 | Fatal, 40 | /// Errors indicate operation failures which can be recovered from without 41 | /// exploding, but the operation itself cannot meaningfully continue. 42 | Error, 43 | /// Warnings indicate oopsies in operation which can be recovered from fully. 44 | Warning, 45 | /// The standard log level. 46 | Info, 47 | /// Debug information are meaningful only when trying to diagnose bogus 48 | /// behaviour or crashing. 49 | Debug, 50 | /// Verbose debug information that creates a printout at every important 51 | /// interaction. Only meaningful in debug conditions. 52 | Trace, 53 | /// The most verbose debug information which also prints raw data from the 54 | /// communication channels. 55 | Data, 56 | 57 | /// The default severity level for bare printouts when a severity is not 58 | /// specified. 59 | Default = Info, 60 | 61 | /// The largest severity value. 62 | Max = None, 63 | /// The lowest severity value. 64 | Min = Data, 65 | }; 66 | 67 | /// The largest verbosity the user can request an increase to. 68 | constexpr std::int8_t MaximumVerbosity = log::Min - log::Default; 69 | /// The smallest verbosity (largest quietness) the user can decrease to. 70 | constexpr std::int8_t MinimumVerbosity = log::Default - log::Max - 1; 71 | 72 | /// The \p Logger class handles emitting log messages to an output device. 73 | /// 74 | /// \note This object is \b NOT thread-safe! 75 | class Logger 76 | { 77 | private: 78 | class OutputBuffer 79 | { 80 | bool Discard; 81 | std::ostream* OS; 82 | std::ostringstream Buffer; 83 | 84 | public: 85 | /// Wraps an output device into a log buffer. 86 | /// 87 | /// \param Discard Whether to throw the logged data away. 88 | OutputBuffer(std::ostream& OS, bool Discard, std::string_view Prefix); 89 | 90 | /// Print the contents of the log buffer to the output device. 91 | ~OutputBuffer() noexcept(false); 92 | 93 | /// Print the contents of the fed value to the internal buffer. 94 | template OutputBuffer& operator<<(T&& Value) 95 | { 96 | if (!Discard) 97 | Buffer << std::forward(Value); 98 | return *this; 99 | } 100 | }; 101 | 102 | /// A global instance of the logger. 103 | static std::unique_ptr Singleton; 104 | 105 | public: 106 | /// \returns a human-readable tag for the specified severity. 107 | static const char* levelName(Severity S) noexcept; 108 | 109 | /// Retrieve the logging instance for the current application. 110 | static Logger& get(); 111 | 112 | /// Retrieve the logging instance for the current application, if any was 113 | /// spawned. Otherwise, returns \p nullptr. 114 | static Logger* tryGet(); 115 | 116 | /// \returns the number of digits (when written in decimal notation) of the 117 | /// given \p Number. 118 | static std::size_t digits(std::size_t Number); 119 | 120 | /// Creates a new \p Logger object that has no connection with the global 121 | /// logging instance. 122 | /// 123 | /// \note In most of the cases, you do not want to do this. 124 | /// 125 | /// \see get() 126 | Logger(Severity SeverityLimit, std::ostream& OS); 127 | 128 | Severity getLimit() const noexcept { return SeverityLimit; } 129 | void setLimit(Severity Limit) noexcept { SeverityLimit = Limit; } 130 | 131 | /// Redirects all log messages after the call to this function to another 132 | /// output device. 133 | void setOutput(std::ostream& OS) noexcept { this->OS = &OS; } 134 | 135 | /// Starts printing a log message with the specified \p S severity. 136 | /// If the \p S severity is lower than the current severity limit, the message 137 | /// will be discarded. 138 | OutputBuffer operator()(Severity S, std::string_view Facility); 139 | 140 | private: 141 | Severity SeverityLimit; 142 | std::ostream* OS; 143 | }; 144 | 145 | #define MONOMUX_LOGGER_SHORTCUT(NAME, SEVERITY) \ 146 | inline decltype(auto) NAME(std::string_view Facility) \ 147 | { \ 148 | return monomux::log::Logger::get()(SEVERITY, Facility); \ 149 | } 150 | 151 | MONOMUX_LOGGER_SHORTCUT(always, None); 152 | MONOMUX_LOGGER_SHORTCUT(log, Default); 153 | 154 | MONOMUX_LOGGER_SHORTCUT(fatal, Fatal); 155 | MONOMUX_LOGGER_SHORTCUT(error, Error); 156 | MONOMUX_LOGGER_SHORTCUT(warn, Warning); 157 | MONOMUX_LOGGER_SHORTCUT(info, Info); 158 | MONOMUX_LOGGER_SHORTCUT(debug, Debug); 159 | MONOMUX_LOGGER_SHORTCUT(trace, Trace); 160 | MONOMUX_LOGGER_SHORTCUT(data, Data); 161 | 162 | #undef MONOMUX_LOGGER_SHORTCUT 163 | 164 | #ifdef MONOMUX_NON_ESSENTIAL_LOGS 165 | /* Wrap logging code into this macro to suppress building it if config option 166 | * \p MONOMUX_NON_ESSENTIAL_LOGS is turned off. 167 | * 168 | * It has been turned \e ON in this build, and trace logging is compiled. 169 | */ 170 | #define MONOMUX_TRACE_LOG(X) MONOMUX_DETAIL_CONDITIONALLY_TRUE(X) 171 | #else /* !MONOMUX_NON_ESSENTIAL_LOGS */ 172 | /* Wrap logging code into this macro to suppress building it if config option 173 | * \p MONOMUX_NON_ESSENTIAL_LOGS is turned off. 174 | * 175 | * It has been turned \b OFF in this build, and trace logging is stripped. 176 | */ 177 | #define MONOMUX_TRACE_LOG(X) MONOMUX_DETAIL_CONDITIONALLY_FALSE(X) 178 | #endif /* MONOMUX_NON_ESSENTIAL_LOGS */ 179 | 180 | } // namespace monomux::log 181 | -------------------------------------------------------------------------------- /include/core/monomux/Version.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | 23 | namespace monomux 24 | { 25 | 26 | struct Version 27 | { 28 | std::size_t Major, Minor, Patch, Build; 29 | std::size_t Offset; 30 | std::string Commit; 31 | bool IsDirty; 32 | }; 33 | 34 | /// \returns the full version information produced by the build system. 35 | Version getVersion(); 36 | 37 | /// \returns a short version string, e.g. \p 1.0.0 38 | std::string getShortVersion(); 39 | 40 | /// \returns a full version string, including additional bits, if any. 41 | std::string getFullVersion(); 42 | 43 | } // namespace monomux 44 | -------------------------------------------------------------------------------- /include/core/monomux/adt/Atomic.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | 23 | namespace monomux 24 | { 25 | 26 | /// Wrapper class over \p std::atomic that enables "copying" and "moving" the 27 | /// value contained by non-atomically initialising a \b new \p atomic with the 28 | /// current contained value. 29 | template class Atomic 30 | { 31 | using AT = std::atomic; 32 | static constexpr bool IsNoExcept = noexcept( 33 | std::declval().store(T{}))&& noexcept(std::declval().load()); 34 | 35 | AT Value = ATOMIC_VAR_INIT(T{}); 36 | 37 | public: 38 | Atomic() noexcept(std::is_nothrow_default_constructible_v) = default; 39 | Atomic(T&& Val) noexcept(IsNoExcept) { Value.store(std::forward(Val)); } 40 | Atomic(const Atomic& RHS) noexcept(IsNoExcept) 41 | { 42 | Value.store(RHS.Value.load()); 43 | } 44 | Atomic(Atomic&& RHS) noexcept(IsNoExcept) { Value.store(RHS.Value.load()); } 45 | Atomic& operator=(const Atomic& RHS) noexcept(IsNoExcept) 46 | { 47 | if (this == &RHS) 48 | return *this; 49 | 50 | Value.store(RHS.Value.load()); 51 | return *this; 52 | } 53 | Atomic& operator=(Atomic&& RHS) noexcept(IsNoExcept) 54 | { 55 | if (this == &RHS) 56 | return *this; 57 | 58 | Value.store(RHS.Value.load()); 59 | return *this; 60 | } 61 | ~Atomic() = default; 62 | 63 | std::atomic& get() noexcept { return Value; } 64 | const std::atomic& get() const noexcept { return Value; } 65 | }; 66 | 67 | } // namespace monomux 68 | -------------------------------------------------------------------------------- /include/core/monomux/adt/Lazy.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | 23 | #include "monomux/adt/POD.hpp" 24 | 25 | namespace monomux 26 | { 27 | 28 | /// Implements a simple, lazy-initialised wrapper of \p T which will construct 29 | /// the object on the first access by running \p EnterFunction. 30 | template > struct Lazy 31 | { 32 | private: 33 | static constexpr bool HasCustomConstructor = 34 | std::is_same_v>; 35 | static constexpr bool IsNoexceptConstruction = 36 | !HasCustomConstructor 37 | ? std::is_nothrow_default_constructible_v 38 | : std::is_nothrow_constructible_v>; 39 | 40 | void wipeStorage() noexcept { detail::memset_manual(Storage, 0, sizeof(T)); } 41 | 42 | public: 43 | /// Constructs a \p Lazy object that will default-initialise the underlying 44 | /// \p T instance. 45 | Lazy() noexcept { wipeStorage(); } 46 | /// Constructs a \p Lazy object that will initialise the underlying instance 47 | /// by executing the \p Enter function. 48 | Lazy(EnterFunction&& Enter) noexcept( 49 | std::is_nothrow_move_constructible_v) 50 | : EnterFn(std::move(Enter)) 51 | { 52 | wipeStorage(); 53 | } 54 | 55 | Lazy(const Lazy& RHS) : Alive(RHS.Alive), EnterFn(RHS.EnterFn) 56 | { 57 | if (Alive) 58 | new (Storage) T{RHS.get()}; 59 | } 60 | Lazy(Lazy&&) = delete; 61 | Lazy& operator=(const Lazy&) = delete; 62 | Lazy& operator=(Lazy&&) = delete; 63 | /// Destroys the underlying \p T instance. 64 | ~Lazy() 65 | { 66 | reinterpret_cast(Storage)->~T(); 67 | wipeStorage(); 68 | Alive = false; 69 | } 70 | 71 | /// \returns the underlying instance. If it is constructed yet, it is first 72 | /// constructed by the \p EnterFunction, if provided, or otherwise default 73 | /// constructed. 74 | T& get() noexcept(IsNoexceptConstruction) 75 | { 76 | if (!Alive) 77 | { 78 | new (Storage) T{EnterFn()}; 79 | Alive = true; 80 | } 81 | 82 | return *reinterpret_cast(Storage); 83 | } 84 | 85 | private: 86 | bool Alive = false; 87 | alignas(T) char Storage[sizeof(T)]; 88 | EnterFunction EnterFn; 89 | }; 90 | 91 | /// Helper function that automatically deduces the type of the \p Lazy instance 92 | /// based on the function (usually a lambda) given to it. 93 | /// 94 | /// Example: 95 | /// 96 | /// auto MyLazy = makeLazy([]() -> some_type { return some_type_maker(); }); 97 | template 98 | auto makeLazy(EnterFunction&& Enter) noexcept( 99 | std::is_nothrow_move_constructible_v) 100 | { 101 | return Lazy, EnterFunction>( 102 | std::forward(Enter)); 103 | } 104 | 105 | } // namespace monomux 106 | -------------------------------------------------------------------------------- /include/core/monomux/adt/MemberFunctionHelper.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | 21 | #define MEMBER_FN_NON_CONST_0(RETURN_TYPE, FUNCTION_NAME) \ 22 | RETURN_TYPE FUNCTION_NAME() \ 23 | { \ 24 | using ConstThisPtr = std::add_pointer_t< \ 25 | std::add_const_t>>; \ 26 | return const_cast( \ 27 | const_cast(this)->FUNCTION_NAME()); \ 28 | } 29 | 30 | #define MEMBER_FN_NON_CONST_0_NOEXCEPT(RETURN_TYPE, FUNCTION_NAME) \ 31 | RETURN_TYPE FUNCTION_NAME() noexcept \ 32 | { \ 33 | using ConstThisPtr = std::add_pointer_t< \ 34 | std::add_const_t>>; \ 35 | return const_cast( \ 36 | const_cast(this)->FUNCTION_NAME()); \ 37 | } 38 | 39 | #define MEMBER_FN_NON_CONST_1( \ 40 | RETURN_TYPE, FUNCTION_NAME, ARG_1_TYPE, ARG_1_NAME) \ 41 | RETURN_TYPE FUNCTION_NAME(ARG_1_TYPE ARG_1_NAME) \ 42 | { \ 43 | using ConstThisPtr = std::add_pointer_t< \ 44 | std::add_const_t>>; \ 45 | return const_cast( \ 46 | const_cast(this)->FUNCTION_NAME(ARG_1_NAME)); \ 47 | } 48 | 49 | #define MEMBER_FN_NON_CONST_1_NOEXCEPT( \ 50 | RETURN_TYPE, FUNCTION_NAME, ARG_1_TYPE, ARG_1_NAME) \ 51 | RETURN_TYPE FUNCTION_NAME(ARG_1_TYPE ARG_1_NAME) noexcept \ 52 | { \ 53 | using ConstThisPtr = std::add_pointer_t< \ 54 | std::add_const_t>>; \ 55 | return const_cast( \ 56 | const_cast(this)->FUNCTION_NAME(ARG_1_NAME)); \ 57 | } 58 | -------------------------------------------------------------------------------- /include/core/monomux/adt/POD.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | 24 | namespace monomux 25 | { 26 | 27 | namespace detail 28 | { 29 | 30 | /// Prevents optimisation of \p memset calls by too clever compilers. 31 | inline void 32 | // NOLINTNEXTLINE(readability-identifier-naming) 33 | memset_manual(void* B, 34 | // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) 35 | int Ch, 36 | std::size_t N) noexcept 37 | { 38 | if (!B || !N) 39 | return; 40 | 41 | volatile auto* P = reinterpret_cast(B); 42 | while (N--) 43 | *P++ = Ch; 44 | } 45 | 46 | } // namespace detail 47 | 48 | /// This class wraps a C-style struct, a "Plain Old Data" (POD) into a C++ 49 | /// structure which ensures that the data is created zero-filled. 50 | template struct POD 51 | { 52 | static_assert(std::is_pod_v, "Only supporting PODs!"); 53 | static_assert(std::is_standard_layout_v && std::is_trivial_v, 54 | "Only supporting PODs!"); 55 | 56 | T& operator*() { return Data; } 57 | const T& operator*() const { return Data; } 58 | T* operator&() { return &Data; } 59 | const T* operator&() const { return &Data; } 60 | T* operator->() { return &Data; } 61 | const T* operator->() const { return &Data; } 62 | operator T&() { return Data; } 63 | operator const T&() const { return Data; } 64 | 65 | /// Creates an empty space where \p T fits. 66 | POD() noexcept 67 | { 68 | static_assert(sizeof(*this) == sizeof(T), "Extra padding is forbidden!"); 69 | reset(); 70 | } 71 | ~POD() noexcept { reset(); } 72 | 73 | POD(const POD& RHS) noexcept { std::memcpy(&Data, &RHS.Data, sizeof(T)); } 74 | 75 | POD(POD&& RHS) noexcept 76 | { 77 | std::memcpy(&Data, &RHS.Data, sizeof(T)); 78 | RHS.reset(); 79 | } 80 | 81 | POD& operator=(const POD& RHS) noexcept 82 | { 83 | if (this == &RHS) 84 | return *this; 85 | 86 | std::memcpy(&Data, &RHS.Data, sizeof(T)); 87 | return *this; 88 | } 89 | 90 | POD& operator=(POD&& RHS) noexcept 91 | { 92 | if (this == &RHS) 93 | return *this; 94 | 95 | std::memcpy(&Data, &RHS.Data, sizeof(T)); 96 | RHS.reset(); 97 | return *this; 98 | } 99 | 100 | /// Zerofill the memory area of the contained object. 101 | void reset() { detail::memset_manual(&Data, 0, sizeof(T)); } 102 | 103 | private: 104 | T Data; 105 | }; 106 | 107 | } // namespace monomux 108 | -------------------------------------------------------------------------------- /include/core/monomux/adt/ScopeGuard.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include "monomux/adt/UniqueScalar.hpp" 21 | 22 | namespace monomux 23 | { 24 | 25 | /// A simple scope guard that fires a callback function (in most cases, a 26 | /// lambda passed to the constructor) when constructed, and when destructed. 27 | /// 28 | /// Example: 29 | /// 30 | /// \code{.cpp} 31 | /// scope_guard RAII{[] { enter(); }, [] { exit(); }}; 32 | /// \endcode 33 | template struct ScopeGuard 34 | { 35 | ScopeGuard(EnterFunction&& Enter, ExitFunction&& Exit) : Exit(Exit) 36 | { 37 | Enter(); 38 | Alive = true; 39 | } 40 | 41 | ~ScopeGuard() 42 | { 43 | if (Alive) 44 | Exit(); 45 | } 46 | 47 | ScopeGuard() = delete; 48 | ScopeGuard(const ScopeGuard&) = delete; 49 | ScopeGuard(ScopeGuard&&) = delete; 50 | ScopeGuard& operator=(const ScopeGuard&) = delete; 51 | ScopeGuard& operator=(ScopeGuard&&) = delete; 52 | 53 | private: 54 | bool Alive; 55 | ExitFunction Exit; 56 | }; 57 | 58 | } // namespace monomux 59 | -------------------------------------------------------------------------------- /include/core/monomux/adt/Tagged.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace monomux 26 | { 27 | 28 | /// Tags a pointer to a type at compile-time with a scalar value that is only 29 | /// descript to clients consuming this object. 30 | /// Otherwise, behaves the same as \p T*. 31 | template class Tagged 32 | { 33 | static constexpr std::size_t Kind = N; 34 | T* Ptr; 35 | 36 | public: 37 | Tagged(T* P) noexcept : Ptr(P) {} 38 | 39 | /// Retrieve the raw tag value. 40 | std::size_t kind() const noexcept { return Kind; } 41 | /// Retrieve the tag value cast to the enum type \p E. 42 | template E kindAs() const noexcept 43 | { 44 | return static_cast(Kind); 45 | } 46 | 47 | bool operator==(Tagged RHS) const { return Ptr == RHS.Ptr; } 48 | bool operator!=(Tagged RHS) const { return Ptr != RHS.Ptr; } 49 | 50 | T* operator->() noexcept { return Ptr; } 51 | const T* operator->() const noexcept { return Ptr; } 52 | T& operator*() noexcept 53 | { 54 | assert(Ptr); 55 | return *Ptr; 56 | } 57 | const T& operator*() const noexcept 58 | { 59 | assert(Ptr); 60 | return *Ptr; 61 | } 62 | T* get() noexcept { return Ptr; } 63 | const T* get() const noexcept { return Ptr; } 64 | }; 65 | 66 | } // namespace monomux 67 | -------------------------------------------------------------------------------- /include/core/monomux/adt/UniqueScalar.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | 23 | namespace monomux 24 | { 25 | 26 | namespace detail 27 | { 28 | 29 | /// Helper class for inheriting the dereference operators on \p UniqueScalar 30 | /// if the entity inside it is a pointer. 31 | template 32 | struct UniqueScalarPointerBase 33 | { 34 | using Type = T; 35 | static_assert(std::is_pointer_v); 36 | using DerefType = std::remove_pointer_t; 37 | using ConstDerefType = std::add_const_t; 38 | using ConstType = std::add_pointer_t; 39 | 40 | DerefType& operator*() { return *get(); } 41 | ConstDerefType& operator*() const { return *get(); } 42 | 43 | Type operator->() noexcept { return get(); } 44 | ConstType operator->() const noexcept { return get(); } 45 | 46 | private: 47 | Type get() noexcept { return static_cast(this)->get(); } 48 | ConstType get() const noexcept 49 | { 50 | return static_cast(this)->get(); 51 | } 52 | }; 53 | 54 | /// Helper class for \e NOT inheriting the dereference operators if the entity 55 | /// is not a pointer. 56 | template 57 | struct UniqueScalarPointerBase 58 | {}; 59 | 60 | } // namespace detail 61 | 62 | /// Wraps a movable scalar type which resets to the default value when 63 | /// moved-from. Unlike \p unique_ptr, the value is allocated in-place. 64 | template 65 | struct UniqueScalar 66 | : detail:: 67 | UniqueScalarPointerBase, std::is_pointer_v> 68 | { 69 | /// Initialises the object with the default value. 70 | UniqueScalar() noexcept(std::is_nothrow_default_constructible_v) 71 | : Value(Default) 72 | { 73 | static_assert(sizeof(*this) == sizeof(T), "Extra padding is forbidden!"); 74 | } 75 | /// Initialises the object with the specified value 76 | UniqueScalar(T Value) noexcept(std::is_nothrow_copy_constructible_v) 77 | : Value(Value) 78 | {} 79 | /// Move-initialises the value from \p RHS, and resets \p RHS to the 80 | /// \p Default. 81 | UniqueScalar(UniqueScalar&& RHS) noexcept( 82 | std::is_nothrow_move_constructible_v&& 83 | std::is_nothrow_move_assignable_v) 84 | : Value(std::move(RHS.Value)) 85 | { 86 | RHS.Value = Default; 87 | } 88 | /// Move-assigns the value from \p RHS, and resets \p RHS to the \p Default. 89 | UniqueScalar& 90 | operator=(UniqueScalar&& RHS) noexcept(std::is_nothrow_move_assignable_v) 91 | { 92 | if (this == &RHS) 93 | return *this; 94 | 95 | Value = std::move(RHS.Value); 96 | RHS.Value = Default; 97 | return *this; 98 | } 99 | ~UniqueScalar() noexcept = default; 100 | 101 | /// Converts to the stored value. 102 | operator T&() noexcept { return Value; } 103 | /// Converts to the stored value. 104 | operator const T&() const noexcept { return Value; } 105 | 106 | /// Converts to the stored value. 107 | T& get() noexcept { return Value; } 108 | /// Converts to the stored value. 109 | const T& get() const noexcept { return Value; } 110 | 111 | /// Sets \p NewValue into the wrapped object. 112 | UniqueScalar& 113 | operator=(T&& NewValue) noexcept(std::is_nothrow_move_assignable_v) 114 | { 115 | Value = std::forward(NewValue); 116 | return *this; 117 | } 118 | 119 | private: 120 | T Value; 121 | }; 122 | 123 | } // namespace monomux 124 | -------------------------------------------------------------------------------- /include/core/monomux/client/ControlClient.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include "Client.hpp" 21 | 22 | namespace monomux::client 23 | { 24 | 25 | /// A \p ControlClient is a non-user-facing management wrapper over an 26 | /// established \p Client. It is used to implement special client-like 27 | /// operations. 28 | /// 29 | /// \note The control client \e wraps a \p Client instance, and as such, will 30 | /// act as a separate client from the ones the user might have attached to the 31 | /// session with. 32 | class ControlClient 33 | { 34 | public: 35 | /// Allows operation in a \p ControlClient \b without attaching to a session. 36 | ControlClient(Client& C); 37 | /// Attaches the \p ControlClient to \p Session to allow session-specific 38 | /// operations. 39 | ControlClient(Client& C, std::string Session); 40 | 41 | const std::string& sessionName() const noexcept { return SessionName; } 42 | 43 | /// Send a request to the server to gracefully detach the latest (in terms of 44 | /// activity) client from the session. 45 | /// 46 | /// \see server::ClientData::lastActive() 47 | void requestDetachLatestClient(); 48 | 49 | /// Send a request to the server to gracefully detach every client from the 50 | /// session. 51 | void requestDetachAllClients(); 52 | 53 | /// Sends a request to the server to gather statistical information and reply 54 | /// it back to this \p Client. 55 | /// 56 | /// \throws std::runtime_error Thrown if communication with the server failed 57 | /// and it did not produce a response that the client could understand. 58 | std::string requestStatistics(); 59 | 60 | private: 61 | Client& BackingClient; 62 | 63 | /// The name of the session the controlling client will send requests to. 64 | std::string SessionName; 65 | }; 66 | 67 | } // namespace monomux::client 68 | -------------------------------------------------------------------------------- /include/core/monomux/client/Dispatch.ipp: -------------------------------------------------------------------------------- 1 | #ifndef DISPATCH 2 | /// Set for the given \p MessageKind the callback \p FUNCTION_NAME. 3 | #define DISPATCH(KIND, FUNCTION_NAME) 4 | #endif 5 | 6 | DISPATCH(ClientIDResponse, responseClientID) 7 | DISPATCH(DetachedNotification, receivedDetachNotification) 8 | 9 | #undef DISPATCH 10 | -------------------------------------------------------------------------------- /include/core/monomux/client/SessionData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | 23 | namespace monomux::client 24 | { 25 | 26 | /// A snaphot view of sessions running on a server, as reported by the server. 27 | /// 28 | /// \see monomux::message::SessionData 29 | /// \see monomux::server::SessionData 30 | struct SessionData 31 | { 32 | std::string Name; 33 | std::chrono::time_point Created; 34 | }; 35 | 36 | } // namespace monomux::client 37 | -------------------------------------------------------------------------------- /include/core/monomux/control/MessageBase.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace monomux::message 26 | { 27 | 28 | /// A global enumeration table of messages that are supported by the protocol. 29 | /// For each entry, an appropriate struct in namespace \p monomux::request or 30 | /// \p monomux::response defines the data members of the message. 31 | enum class MessageKind : std::uint16_t 32 | { 33 | /// Indicates a broken message that failed to read as a proper entity. 34 | Invalid = 0, 35 | 36 | /// Indicates a subobject of a \p Message that cannot be understood 37 | /// individually. 38 | Base, 39 | 40 | /// A status message from the server to the client about the details of the 41 | /// established connection. 42 | ConnectionNotification, 43 | 44 | /// A request to the server to reply the client's ID to the client. 45 | ClientIDRequest, 46 | /// A response for the \p ClientIDRequest, containing the client's ID. 47 | ClientIDResponse, 48 | 49 | /// A request to the server to associate the connection with another client, 50 | /// marking it as the data connection/socket. 51 | DataSocketRequest, 52 | /// A response for the \p DataSocketRequest, indicating whether the 53 | /// request was accepted. 54 | DataSocketResponse, 55 | 56 | /// A request to the server to reply with data about the running sessions on 57 | /// the server. 58 | SessionListRequest, 59 | /// A response to the \p SessionListRequest, containing \p Session data. 60 | SessionListResponse, 61 | 62 | /// A request to the server to create a brand new new session. 63 | MakeSessionRequest, 64 | /// A reponse to the \p MakeSessionRequest containing the results of the new 65 | /// session. 66 | MakeSessionResponse, 67 | 68 | /// A request to the server to have the sending client attached to a session. 69 | AttachRequest, 70 | /// A response to the \p AttachRequest containing whether the attaching 71 | /// succeeded. 72 | AttachResponse, 73 | 74 | /// A request to the server to detach one or more clients from a session. 75 | DetachRequest, 76 | /// A response to the \p DetachRequest containing the result of the operation. 77 | DetachResponse, 78 | 79 | /// A notification sent by the server to a client indicating that the client 80 | /// had been detached. 81 | DetachedNotification, 82 | 83 | /// A request to the server to send a \p signal to the attached session. 84 | SignalRequest, 85 | 86 | /// A notification to the server to apply window resize/redraw to an attached 87 | /// session. 88 | RedrawNotification, 89 | 90 | /// A request to the server to respond with statistical information about the 91 | /// execution. 92 | StatisticsRequest, 93 | /// A response to the \p StatisticsRequest. 94 | StatisticsResponse, 95 | }; 96 | 97 | /// Helper class that contains the parsed \p MessageKind of a \p Message, and 98 | /// the remaining, not yet parsed \p Buffer. 99 | struct Message 100 | { 101 | MessageKind Kind; 102 | std::string_view RawData; 103 | 104 | /// Encodes the given number as a platform-specific binary-string. 105 | static std::string sizeToBinaryString(std::size_t N); 106 | 107 | /// Decodes a \p std::size_t from the given \p Str. 108 | static std::size_t binaryStringToSize(std::string_view Str) noexcept; 109 | 110 | /// Encodes the \p Kind variable as a binary string for appropriately 111 | /// prefixing a transmissible buffer. 112 | std::string encodeKind() const; 113 | 114 | /// Decodes the binary prefix of a message as a \p Kind. 115 | static MessageKind decodeKind(std::string_view Str) noexcept; 116 | 117 | /// Pack a raw and encoded message into a full transmissible payload. 118 | std::string pack() const; 119 | 120 | /// Unpack an encoded and fully read payload into its base constitutents. 121 | static Message unpack(std::string_view Str) noexcept; 122 | }; 123 | 124 | /// Encodes a message object into its raw data form. 125 | template std::string encode(const T& Msg) 126 | { 127 | std::string RawForm = T::encode(Msg); 128 | 129 | Message MB; 130 | MB.Kind = Msg.Kind; 131 | MB.RawData = RawForm; 132 | 133 | return MB.pack(); 134 | } 135 | 136 | /// Encodes a message object into its raw data form, prefixed with a payload 137 | /// size. 138 | template std::string encodeWithSize(const T& Msg) 139 | { 140 | std::string Payload = encode(Msg); 141 | return Message::sizeToBinaryString(Payload.size()) + std::move(Payload); 142 | } 143 | 144 | /// Decodes the given received buffer as a specific message object, and returns 145 | /// it if successful. 146 | template std::optional decode(std::string_view Str) noexcept 147 | { 148 | Message MB = Message::unpack(Str); 149 | if (MB.Kind == MessageKind::Invalid) 150 | return std::nullopt; 151 | 152 | std::optional Msg = T::decode(MB.RawData); 153 | return Msg; 154 | } 155 | 156 | } // namespace monomux::message 157 | -------------------------------------------------------------------------------- /include/core/monomux/control/PascalString.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include "monomux/control/MessageBase.hpp" 21 | #include "monomux/system/BufferedChannel.hpp" 22 | 23 | namespace monomux::message 24 | { 25 | 26 | /// Sends a specific message, fully encoded for transportation, on the 27 | /// \p Channel. 28 | /// 29 | /// \note This operation \b MAY block. 30 | template 31 | std::size_t sendMessage(BufferedChannel& Channel, const T& Msg) 32 | { 33 | return Channel.write(encodeWithSize(Msg)); 34 | } 35 | 36 | /// Reads a size-prefixed payload from the \p Channel. 37 | /// 38 | /// \note This operation \b MAY block. 39 | std::string readPascalString(BufferedChannel& Channel); 40 | 41 | /// Reads a fully encoded message from the \p Channel and expects it to be of 42 | /// message type \p T. If the message successfully read, returns it. 43 | /// 44 | /// \note This operation \b MAY block. If the message fails to read, or the 45 | /// message is not the \e expected type, the message \b MAY be dropped and lost. 46 | template std::optional receiveMessage(BufferedChannel& Channel) 47 | { 48 | std::string Data = readPascalString(Channel); 49 | Message MsgBase = Message::unpack(Data); 50 | if (MsgBase.Kind != T::Kind) 51 | return std::nullopt; 52 | 53 | std::optional Msg = T::decode(MsgBase.RawData); 54 | return Msg; 55 | } 56 | 57 | } // namespace monomux::message 58 | -------------------------------------------------------------------------------- /include/core/monomux/server/ClientData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | 24 | #include "monomux/control/Message.hpp" 25 | #include "monomux/system/Socket.hpp" 26 | 27 | namespace monomux::server 28 | { 29 | 30 | class SessionData; 31 | 32 | /// Stores information about and associated resources to a connected client. 33 | class ClientData 34 | { 35 | public: 36 | ClientData(std::unique_ptr Connection); 37 | 38 | std::size_t id() const noexcept { return ID; } 39 | /// Returns the most recent random-generated nonce for this client, and 40 | /// clear it from memory, rendering it useless in later authentications. 41 | std::size_t consumeNonce() noexcept; 42 | /// Creates a new random number for the client's authentication, and returns 43 | /// it. The value is stored for until used. 44 | std::size_t makeNewNonce() noexcept; 45 | 46 | std::chrono::time_point 47 | whenCreated() const noexcept 48 | { 49 | return Created; 50 | } 51 | std::chrono::time_point lastActive() const noexcept 52 | { 53 | return LastActivity; 54 | } 55 | void activity() noexcept { LastActivity = std::chrono::system_clock::now(); } 56 | 57 | Socket& getControlSocket() noexcept { return *ControlConnection; } 58 | Socket* getDataSocket() noexcept { return DataConnection.get(); } 59 | 60 | /// Releases the control socket of the other client and associates it as the 61 | /// data connection of the current client. 62 | void subjugateIntoDataSocket(ClientData& Other) noexcept; 63 | 64 | SessionData* getAttachedSession() noexcept { return AttachedSession; } 65 | const SessionData* getAttachedSession() const noexcept 66 | { 67 | return AttachedSession; 68 | } 69 | void detachSession() noexcept { AttachedSession = nullptr; } 70 | void attachToSession(SessionData& Session) noexcept 71 | { 72 | AttachedSession = &Session; 73 | } 74 | 75 | /// Sends the specified detachment reason to the client, if it is connected. 76 | /// 77 | /// \param EC The exit code of the session that is detaching from. Not always 78 | /// meaningful. 79 | /// \param Reason The reason behind the detachment. Not always meaningful. 80 | void sendDetachReason(monomux::message::notification::Detached::DetachMode R, 81 | int EC = 0, 82 | std::string Reason = {}); 83 | 84 | private: 85 | std::size_t ID; 86 | std::optional Nonce; 87 | /// The timestamp when the client connected. 88 | std::chrono::time_point Created; 89 | /// The timestamp when the client was most recently trasmitting \b data. 90 | std::chrono::time_point LastActivity; 91 | 92 | /// The control connection transcieves control information and commands. 93 | std::unique_ptr ControlConnection; 94 | 95 | /// The data connection transcieves the actual program data. 96 | std::unique_ptr DataConnection; 97 | 98 | /// \e If the client is attached to a session, points to the data record of 99 | /// the session. 100 | SessionData* AttachedSession; 101 | }; 102 | 103 | } // namespace monomux::server 104 | -------------------------------------------------------------------------------- /include/core/monomux/server/Dispatch.ipp: -------------------------------------------------------------------------------- 1 | #ifndef DISPATCH 2 | /// Set for the given \p MessageKind the callback \p FUNCTION_NAME. 3 | #define DISPATCH(KIND, FUNCTION_NAME) 4 | #endif 5 | 6 | DISPATCH(ClientIDRequest, requestClientID) 7 | DISPATCH(DataSocketRequest, requestDataSocket) 8 | 9 | DISPATCH(SessionListRequest, requestSessionList) 10 | DISPATCH(MakeSessionRequest, requestMakeSession) 11 | DISPATCH(AttachRequest, requestAttach) 12 | DISPATCH(DetachRequest, requestDetach) 13 | 14 | DISPATCH(SignalRequest, signalSession) 15 | 16 | DISPATCH(RedrawNotification, redrawNotified) 17 | 18 | DISPATCH(StatisticsRequest, statisticsRequest) 19 | 20 | #undef DISPATCH 21 | -------------------------------------------------------------------------------- /include/core/monomux/server/SessionData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "monomux/system/Process.hpp" 27 | 28 | namespace monomux::server 29 | { 30 | 31 | class ClientData; 32 | 33 | /// Encapsulates a running session under the server owning the instance. 34 | class SessionData 35 | { 36 | public: 37 | SessionData(std::string Name) 38 | : Name(std::move(Name)), Created(std::chrono::system_clock::now()) 39 | {} 40 | 41 | const std::string& name() const noexcept { return Name; } 42 | std::chrono::time_point 43 | whenCreated() const noexcept 44 | { 45 | return Created; 46 | } 47 | std::chrono::time_point lastActive() const noexcept 48 | { 49 | return LastActivity; 50 | } 51 | void activity() noexcept { LastActivity = std::chrono::system_clock::now(); } 52 | 53 | bool hasProcess() const noexcept { return MainProcess.has_value(); } 54 | void setProcess(Process&& Process) noexcept; 55 | Process& getProcess() noexcept 56 | { 57 | assert(hasProcess()); 58 | return *MainProcess; 59 | } 60 | const Process& getProcess() const noexcept 61 | { 62 | assert(hasProcess()); 63 | return *MainProcess; 64 | } 65 | 66 | /// \returns a file descriptor that can be used as a key to identify 67 | /// the connection towards the session. 68 | raw_fd getIdentifyingFD() const noexcept; 69 | 70 | /// \returns the connection through which data can be read from the session, 71 | /// if there is any. 72 | Pipe* getReader() noexcept 73 | { 74 | if (!hasProcess() || !getProcess().hasPty()) 75 | return nullptr; 76 | return &getProcess().getPty()->reader(); 77 | } 78 | /// \returns the connection through which data can be sent to the session, 79 | /// if there is any. 80 | Pipe* getWriter() noexcept 81 | { 82 | if (!hasProcess() || !getProcess().hasPty()) 83 | return nullptr; 84 | return &getProcess().getPty()->writer(); 85 | } 86 | 87 | const std::vector& getAttachedClients() const noexcept 88 | { 89 | return AttachedClients; 90 | } 91 | /// \returns the \p ClientData from all attached client which \p activity() 92 | /// field is the newest (most recently active client). 93 | ClientData* getLatestClient() const; 94 | void attachClient(ClientData& Client); 95 | void removeClient(ClientData& Client) noexcept; 96 | 97 | private: 98 | /// A user-given identifier for the session. 99 | std::string Name; 100 | /// The timestamp when the session was spawned. 101 | std::chrono::time_point Created; 102 | /// The timestamp when the underlying program was most recently trasmitted 103 | /// data. 104 | std::chrono::time_point LastActivity; 105 | 106 | /// The process (if any) executing in the session. 107 | /// 108 | /// \note This is only set when the session is spawned with a process, and 109 | /// stores no information about the underlying process, outside of Monomux's 110 | /// control, changing its image via an \p exec() call... 111 | std::optional MainProcess; 112 | 113 | /// The list of clients currently attached to this session. 114 | std::vector AttachedClients; 115 | }; 116 | 117 | } // namespace monomux::server 118 | -------------------------------------------------------------------------------- /include/core/monomux/system/Channel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | 24 | #include "monomux/adt/UniqueScalar.hpp" 25 | #include "monomux/system/fd.hpp" 26 | 27 | namespace monomux 28 | { 29 | 30 | /// Wraps a system resource used for communication. This is a very low-level 31 | /// interface encapsulating the necessary system calls and transmission logic. 32 | class Channel 33 | { 34 | public: 35 | Channel() = delete; 36 | 37 | /// \returns the raw, unmanaged file descriptor for the underlying resource. 38 | raw_fd raw() const noexcept { return Handle.get(); } 39 | 40 | /// Steal the \p Handle file descriptor from the current communication channel 41 | /// marking it failed and preventing the cleanup of the resource. 42 | fd release() &&; 43 | 44 | /// \returns the user-friendly identifier of the communication channel. This 45 | /// might be empty, a transient label, or sometimes a path on the filesystem. 46 | const std::string& identifier() const noexcept { return Identifier; } 47 | 48 | /// \returns whether an operation failed and indicated that the underlying 49 | /// resource had broken. 50 | bool failed() const noexcept { return !Handle.has() || Failed; } 51 | 52 | /// Read at maximum \p Bytes bytes of data from the communication channel. 53 | /// 54 | /// \warning Depending on the implementation of the OS primitive and its 55 | /// state, this operation \b MAY block, or \b MAY return less than exactly 56 | /// \p Bytes of data, or even fail completely, if the channel does not support 57 | /// reading. It is also possible that the underlying implementation reads 58 | /// \b more than \p Bytes of data, in which case the tail end is discarded. 59 | std::string read(std::size_t Bytes); 60 | 61 | /// Write the contents of \p Buffer into the communication channel. 62 | /// 63 | /// \warning Depending on the implementation of the OS primitive and its 64 | /// state, this operation \b MAY block, or \b MAY write less than the entire 65 | /// buffer. In some cases, it also \b MAY fail completely, if the channel does 66 | /// not support writing. 67 | /// 68 | /// \returns the number of bytes written, as reported by the operating system. 69 | std::size_t write(std::string_view Buffer); 70 | 71 | virtual ~Channel() noexcept = default; 72 | 73 | protected: 74 | Channel(fd Handle, std::string Identifier, bool NeedsCleanup); 75 | Channel(Channel&&) noexcept = default; 76 | Channel& operator=(Channel&&) noexcept = default; 77 | 78 | /// Implemented by subclases to actually perform reading from the system. 79 | /// 80 | /// \param Continue Whether the read operation from the low-level resource 81 | /// might continue, because there is more data available. 82 | virtual std::string readImpl(std::size_t Bytes, bool& Continue) = 0; 83 | /// Implemented by subclases to actually perform writing to the system. 84 | /// 85 | /// \param Continue Whether the write operation to the low-level resource 86 | /// might continue, because there is more space available. 87 | virtual std::size_t writeImpl(std::string_view Buffer, bool& Continue) = 0; 88 | 89 | bool needsCleanup() const noexcept { return EntityCleanup; } 90 | void setFailed() noexcept { Failed = true; } 91 | 92 | fd Handle; 93 | std::string Identifier; 94 | 95 | private: 96 | UniqueScalar EntityCleanup; 97 | UniqueScalar Failed; 98 | }; 99 | 100 | } // namespace monomux 101 | -------------------------------------------------------------------------------- /include/core/monomux/system/CheckedPOSIX.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace monomux 26 | { 27 | 28 | // Hardcode this, because decltype(errno) would not be portable. 29 | // errno might be defined only as a macro! 30 | using errno_t = int; 31 | 32 | namespace detail 33 | { 34 | 35 | template struct Result 36 | { 37 | private: 38 | R Value; 39 | bool Errored; 40 | std::error_code ErrorCode; 41 | 42 | public: 43 | Result(R&& Value, bool Errored, std::error_code Error) 44 | : Value(std::move(Value)), Errored(Errored), ErrorCode(Error) 45 | {} 46 | 47 | explicit operator bool() const noexcept { return !Errored; } 48 | std::error_code getError() const noexcept { return ErrorCode; } 49 | R& get() noexcept { return Value; } 50 | const R& get() const noexcept { return Value; } 51 | }; 52 | 53 | template <> struct Result 54 | { 55 | private: 56 | bool Errored; 57 | std::error_code ErrorCode; 58 | 59 | public: 60 | Result(bool Errored, std::error_code Error) 61 | : Errored(Errored), ErrorCode(Error) 62 | {} 63 | 64 | explicit operator bool() const noexcept { return !Errored; } 65 | std::error_code getError() const noexcept { return ErrorCode; } 66 | }; 67 | 68 | } // namespace detail 69 | 70 | /// Allows executing a system call with automatically handled \p errno checking. 71 | /// 72 | /// Clients MUST pass a lambda that returns the value of the system call, and 73 | /// list ALL the values which might indicate a FAILED system call. 74 | /// 75 | /// The result of the call itself is obtainable from the return value of this 76 | /// function. 77 | /// 78 | /// Example: 79 | /// 80 | /// \code{.cpp} 81 | /// auto Open = CheckedPOSIX([]() { 82 | /// return ::open("foo", O_RDONLY); 83 | /// }, /* ErrorIndicatingReturnValue =*/-1); 84 | /// 85 | /// if (!Open) { 86 | /// // Do something as the call failed. 87 | /// } 88 | /// Open.get(); // Obtain the return value from the lambda. 89 | /// \endcode 90 | template 91 | decltype(auto) 92 | // NOLINTNEXTLINE(readability-identifier-naming) 93 | CheckedPOSIX(Fn&& F, ErrTys&&... ErrorValues) noexcept 94 | { 95 | using namespace monomux::detail; 96 | static_assert(!std::is_same_v, 97 | "Lambda must return something!"); 98 | 99 | auto ReturnValue = F(); 100 | bool Errored = (false || ... || (ReturnValue == ErrorValues)); 101 | return Result{ 102 | std::move(ReturnValue), 103 | Errored, 104 | std::make_error_code(static_cast(errno))}; 105 | } 106 | 107 | /// Allows executing a system call with translating an error to an exception. 108 | /// 109 | /// Clients MUST pass a lambda that returns the value of the system call, and 110 | /// list ALL the values which might indicate a FAILED system call. 111 | /// 112 | /// The result of the call itself is returned by this function. 113 | /// If the call fails, this function throws an exception. 114 | /// 115 | /// Example: 116 | /// 117 | /// \code{.cpp} 118 | /// auto FD = CheckedPOSIXThrow([]() { 119 | /// return ::open("foo", O_RDONLY); 120 | /// }, "Failed to open the file"s, 121 | /// /* ErrorIndicatingReturnValue =*/-1); 122 | /// 123 | /// FD; // Obtain the return value from the lambda. 124 | /// \endcode 125 | template 126 | decltype(auto) 127 | // NOLINTNEXTLINE(readability-identifier-naming) 128 | CheckedPOSIXThrow(Fn&& F, std::string ErrMsg, ErrTys&&... ErrorValues) 129 | { 130 | auto Result = 131 | CheckedPOSIX(std::forward(F), std::forward(ErrorValues)...); 132 | if (!Result) 133 | throw std::system_error{Result.getError(), ErrMsg}; 134 | 135 | // Make sure to not return an `int &` or something similar dangling! 136 | std::remove_reference_t Copy = Result.get(); 137 | return Copy; 138 | } 139 | 140 | /// Allows executing a system call with automatically handled \p errno checking. 141 | /// 142 | /// This function allows for complex logic inside the callback, and complex 143 | /// indication of errors. 144 | /// 145 | /// Clients are encouraged to pass a lambda to which does the system call. 146 | /// This lambda MUST take a single parameter, `bool& Error`, which the 147 | /// caller MUST set to \p true if the syscall returned an error, appropriately. 148 | /// The lambda MIGHT return additional values, which are obtainable from the 149 | /// result of this call. 150 | /// 151 | /// Example: 152 | /// 153 | /// \code{.cpp} 154 | /// auto Open = CheckedPOSIX([](bool& Error) { 155 | /// int fd = ::open("foo", O_RDONLY); 156 | /// Error = (fd == -1); 157 | /// return 42; 158 | /// }); 159 | /// 160 | /// if (!Open) { 161 | /// // Do something as the call failed. 162 | /// } 163 | /// Open.get(); // Obtain the return value from the lambda. 164 | /// \endcode 165 | template 166 | decltype(auto) 167 | CheckedPOSIX(Fn&& F) noexcept // NOLINT(readability-identifier-naming) 168 | { 169 | using namespace monomux::detail; 170 | bool Errored = false; 171 | using result_type = decltype(F(Errored)); // What is the lambda returning? 172 | 173 | if constexpr (std::is_same_v) 174 | { 175 | F(Errored); 176 | return Result{Errored, 177 | std::make_error_code(static_cast(errno))}; 178 | } 179 | else 180 | { 181 | result_type R = F(Errored); 182 | return Result{ 183 | std::move(R), 184 | Errored, 185 | std::make_error_code(static_cast(errno))}; 186 | } 187 | } 188 | 189 | } // namespace monomux 190 | -------------------------------------------------------------------------------- /include/core/monomux/system/Environment.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace monomux 26 | { 27 | 28 | /// \returns the value of the environment variable \p Key. 29 | /// 30 | /// \note This function is a safe alternative to \p getenv() as it immediately 31 | /// allocates a \e new string with the result. 32 | std::string getEnv(const std::string& Key); 33 | 34 | /// \returns the default shell (command interpreter) for the current user. 35 | std::string defaultShell(); 36 | 37 | struct SocketPath 38 | { 39 | /// \returns the default directory where a server socket should be placed for 40 | /// the current user. 41 | static SocketPath defaultSocketPath(); 42 | 43 | /// Transforms the specified \p Path into a split \p SocketPath object. 44 | static SocketPath absolutise(const std::string& Path); 45 | 46 | /// \returns the \p Path and \p Filename concatenated appropriately. 47 | std::string toString() const; 48 | 49 | std::string Path; 50 | std::string Filename; 51 | 52 | /// Whether the \p Path value (without the \p Filename) is likely specific to 53 | /// the current user. 54 | bool IsPathLikelyUserSpecific; 55 | }; 56 | 57 | /// Allows crafting and retrieving information about a running Monomux session 58 | /// injected through the use of environment variables. 59 | struct MonomuxSession 60 | { 61 | SocketPath Socket; 62 | std::string SessionName; 63 | 64 | std::vector> createEnvVars() const; 65 | static std::optional loadFromEnv(); 66 | }; 67 | 68 | } // namespace monomux 69 | -------------------------------------------------------------------------------- /include/core/monomux/system/Event.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "monomux/adt/MemberFunctionHelper.hpp" 28 | #include "monomux/adt/POD.hpp" 29 | #include "monomux/adt/SmallIndexMap.hpp" 30 | #include "monomux/system/fd.hpp" 31 | 32 | namespace monomux 33 | { 34 | 35 | /// A type-safe wrapper over an \p epoll(7) event polling structure. 36 | /// \p epoll(7) works as an I/O event notification system similarly to the 37 | /// hopefully widely known \p select(2) kernel functionality. 38 | /// 39 | /// \p EPoll registers a file descriptor internal to the proces which will be 40 | /// notified by the kernel if some of the registered files undergo an I/O 41 | /// change, such as data becoming available on a socket. 42 | /// 43 | /// Using \p eventfd(2), this implementation is also capable of having events 44 | /// crafted by clients appear as if they were created by the kernel. 45 | class EPoll 46 | { 47 | friend class Listener; 48 | /// Helper RAII object that manages assigning a file descriptor into the 49 | /// listen-set of an \p epoll(7) structure. 50 | class Listener 51 | { 52 | EPoll& Master; 53 | raw_fd FDToListenFor; 54 | 55 | public: 56 | Listener(EPoll& Master, raw_fd FD, bool Incoming, bool Outgoing); 57 | ~Listener(); 58 | }; 59 | 60 | public: 61 | /// Create a new \p epoll(7) structure associated with the current process. 62 | /// 63 | /// The structure is initialised to support at most \p EventCount events. 64 | EPoll(std::size_t EventCount); 65 | 66 | ~EPoll(); 67 | 68 | /// Get the number of events that fired in the last successful \p wait(). 69 | std::size_t getEventCount() const noexcept { return NotificationCount; } 70 | /// Get the number of events that were manually scheduled by the client in the 71 | /// last successful \p wait(). 72 | std::size_t getScheduledCount() const noexcept 73 | { 74 | return ScheduledResult.size(); 75 | } 76 | 77 | std::size_t getMaxEventCount() const noexcept { return Notifications.size(); } 78 | 79 | /// Blocks and waits until there is a notification that signalled the event 80 | /// watcher. 81 | /// 82 | /// \return The number of events received, either from the system or by 83 | /// manual scheduling. 84 | std::size_t wait(); 85 | 86 | /// Retrieve the Nth event. 87 | const struct ::epoll_event& operator[](std::size_t Index) const 88 | { 89 | return at(Index); 90 | } 91 | /// Retrieve the Nth event. 92 | MEMBER_FN_NON_CONST_1(struct ::epoll_event&, operator[], std::size_t, Index); 93 | /// Retrieve the Nth event. 94 | const struct ::epoll_event& at(std::size_t Index) const; 95 | /// Retrieve the Nth event. 96 | MEMBER_FN_NON_CONST_1(struct ::epoll_event&, at, std::size_t, Index); 97 | 98 | /// Retrieve the file descriptor that fired for the Nth event. 99 | raw_fd fdAt(std::size_t Index) noexcept; 100 | 101 | struct EventWithMode 102 | { 103 | raw_fd FD; 104 | bool Incoming; 105 | bool Outgoing; 106 | }; 107 | /// Retrieve the Nth event. 108 | EventWithMode eventAt(std::size_t Index) noexcept; 109 | 110 | /// Adds the specified file descriptor \p FD to the event queue. Events will 111 | /// trigger for \p Incoming (the file is available for reading) or \p Outgoing 112 | /// (the file is available for writing) operations. 113 | void listen(raw_fd FD, bool Incoming, bool Outgoing); 114 | 115 | /// Stop listening for changes of \p FD. 116 | void stop(raw_fd FD); 117 | 118 | /// Stop listening on \b all associated file descriptors. 119 | void clear(); 120 | 121 | /// Explicitly schedule the file descriptor \p FD to appear in the event queue 122 | /// even if the system generates no event notification for it. 123 | /// 124 | /// \p Incoming and \p Outgoing decides which flag(s) the event will appear 125 | /// as. Scheduled events are placed \b before system notifications in the 126 | /// result \b after a call to \p wait(), but do not \e override system 127 | /// results. A file descriptor both "hand-scheduled" and system notified will 128 | /// appear twice in the result array. 129 | void schedule(raw_fd FD, bool Incoming, bool Outgoing); 130 | 131 | private: 132 | std::size_t NotificationCount = 0; 133 | /// The file descriptor registered in the system for the event structure. 134 | fd MasterFD; 135 | std::map Listeners; 136 | 137 | /// Contains the events that fired and triggered a notification from the 138 | /// system. 139 | std::vector> Notifications; 140 | /// Contains the events that were manually scheduled before the most recent 141 | /// \p wait() call. 142 | std::vector> ScheduledResult; 143 | 144 | /// The file descriptor registered in the system for the manually scheduled 145 | /// event callbacks. 146 | fd ScheduleFD; 147 | /// Contains the index in the \p Notifications vector, after a successful call 148 | /// to \p wait(), where the \p ScheduleFD's notification was placed. 149 | std::optional ScheduleFDNotifiedAtIndex; 150 | 151 | static const std::size_t FDLookupSize = 256; 152 | /// Contains the events that were manually scheduled by the client before a 153 | /// call to \p wait(). After \p wait() is called, the events are moved to 154 | /// the \p ScheduledResult list to be accessed appropriately. 155 | std::vector> ScheduledWaiting; 156 | /// Map file descriptor values to existing records in the \p ScheduledWaiting 157 | /// vector. Used only to de-duplicate the same file descriptor being scheduled 158 | /// more than once. 159 | SmallIndexMap 163 | ScheduledWaitingMap; 164 | 165 | bool isValidIndex(std::size_t I) const noexcept; 166 | }; 167 | 168 | } // namespace monomux 169 | -------------------------------------------------------------------------------- /include/core/monomux/system/Pipe.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "monomux/adt/UniqueScalar.hpp" 26 | #include "monomux/system/BufferedChannel.hpp" 27 | #include "monomux/system/fd.hpp" 28 | 29 | namespace monomux 30 | { 31 | 32 | /// A pipe is a one-way communication channel between a reading and a writing 33 | /// end. 34 | /// 35 | /// Data written to the pipe's write end is buffered by the kernel and 36 | /// can be read on the read end. 37 | /// 38 | /// This class wraps a nameless pipe or a Unix named pipe (\e FIFO) appearing as 39 | /// a file in the filesystem, and allows reading or writing (but noth both!) to 40 | /// it. 41 | class Pipe : public BufferedChannel 42 | { 43 | public: 44 | /// The mode with which the \p Pipe is opened. 45 | enum Mode 46 | { 47 | /// Sentinel. 48 | None = 0, 49 | 50 | /// Open the read end of the pipe. 51 | Read = O_RDONLY, 52 | /// Open the write end of the pipe. 53 | Write = O_WRONLY 54 | }; 55 | 56 | /// Wrapper for the return type of \p create() which creates an anonymous 57 | /// pipe that only exists as file descriptors. 58 | struct AnonymousPipe 59 | { 60 | /// \returns the pipe for the read end. 61 | Pipe* getRead() const noexcept { return Read.get(); } 62 | /// \returns the pipe for the write end. 63 | Pipe* getWrite() const noexcept { return Write.get(); } 64 | 65 | /// Take ownership for the read end of the pipe, and close the write end. 66 | std::unique_ptr takeRead(); 67 | /// Take ownership for the write end of the pipe, and close the read end. 68 | std::unique_ptr takeWrite(); 69 | 70 | private: 71 | friend class Pipe; 72 | 73 | std::unique_ptr Read; 74 | std::unique_ptr Write; 75 | }; 76 | 77 | /// Creates a new named pipe (\e FIFO) which will be owned by the current 78 | /// instance, and cleaned up on exit. 79 | /// 80 | /// This call opens the \p Pipe in \p Write mode. 81 | /// 82 | /// If \p InheritInChild is true, the pipe will be flagged to be inherited 83 | /// by a potential child process. 84 | /// 85 | /// \see mkfifo(3) 86 | static Pipe create(std::string Path, bool InheritInChild = false); 87 | 88 | /// Creates a new unnamed pipe which will be owned by the current instance, 89 | /// and cleaned up on exit. 90 | static AnonymousPipe create(bool InheritInChild = false); 91 | 92 | /// Opens a connection to the named pipe (\e FIFO) existing at \p Path. The 93 | /// connection will be cleaned up during destruction, but no attempts will be 94 | /// made to destroy the named entity itself. 95 | /// 96 | /// If \p InheritInChild is true, the socket will be flagged to be inherited 97 | /// by a potential child process. 98 | /// 99 | /// \see open(2) 100 | static Pipe 101 | open(std::string Path, Mode OpenMode = Read, bool InheritInChild = false); 102 | 103 | /// Wraps the already existing file descriptor \p FD as a \p Pipe. The new 104 | /// instance will take ownership and close the resource at the end of its 105 | /// life. 106 | /// 107 | /// \param Identifier An identifier to assign to the \p Pipe. If empty, a 108 | /// default value will be created. 109 | /// 110 | /// \note This method does \b NOT verify whether the wrapped file descriptor 111 | /// is indeed a pipe, and assumes it is set up appropriately. 112 | static Pipe wrap(fd&& FD, Mode OpenMode = Read, std::string Identifier = ""); 113 | 114 | /// Wraps the already existing file descriptor \p FD as a \p Pipe. The new 115 | /// instance will \b NOT \b TAKE ownership or close the resource at the end 116 | /// of its life. 117 | /// 118 | /// \param Identifier An identifier to assign to the \p Pipe. If empty, a 119 | /// default value will be created. 120 | /// 121 | /// \note This method does \b NOT verify whether the wrapped file descriptor 122 | /// is indeed a pipe, and assumes it is set up appropriately. 123 | static Pipe 124 | weakWrap(raw_fd FD, Mode OpenMode = Read, std::string Identifier = ""); 125 | 126 | /// Sets the open pipe to be \e non-blocking. \p Read operations will 127 | /// immediately return without data, and \p Write will fail if the pipe is 128 | /// full of unread contents. 129 | void setBlocking(); 130 | /// Sets the open pipe to be \b blocking. \p Read operations will wait until 131 | /// data is available, and \p Write into a full pipe will wait until a reader 132 | /// consumed enough data. 133 | void setNonblocking(); 134 | 135 | bool isBlocking() const noexcept { return !Nonblock; } 136 | bool isNonblocking() const noexcept { return Nonblock; } 137 | 138 | /// Directly read and consume at most \p Bytes of data from the given file 139 | /// descriptor \p FD. 140 | /// 141 | /// \param Success If not \p nullptr, and the read encounters an error, will 142 | /// be set to \p false. 143 | // static std::string 144 | // read(raw_fd FD, std::size_t Bytes, bool* Success = nullptr); 145 | 146 | /// Write \p Data into the file descriptor \p FD. 147 | /// 148 | /// \param Success If not \p nullptr, and the write encounters an error, will 149 | /// be set to \p false. 150 | /// 151 | /// \return The number of bytes written. 152 | // static std::size_t 153 | // write(raw_fd FD, std::string_view Buffer, bool* Success = nullptr); 154 | 155 | ~Pipe() noexcept override; 156 | Pipe(Pipe&&) noexcept = default; 157 | Pipe& operator=(Pipe&&) noexcept = default; 158 | 159 | using BufferedChannel::read; 160 | using BufferedChannel::write; 161 | 162 | std::size_t optimalReadSize() const noexcept override; 163 | std::size_t optimalWriteSize() const noexcept override; 164 | 165 | protected: 166 | Pipe(fd Handle, std::string Identifier, bool NeedsCleanup, Mode OpenMode); 167 | 168 | std::string readImpl(std::size_t Bytes, bool& Continue) override; 169 | std::size_t writeImpl(std::string_view Buffer, bool& Continue) override; 170 | 171 | private: 172 | UniqueScalar OpenedAs; 173 | UniqueScalar Nonblock; 174 | UniqueScalar Weak; 175 | }; 176 | 177 | } // namespace monomux 178 | -------------------------------------------------------------------------------- /include/core/monomux/system/Process.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "monomux/system/CheckedPOSIX.hpp" 29 | #include "monomux/system/Pty.hpp" 30 | 31 | namespace monomux 32 | { 33 | 34 | /// Responsible for creating, executing, and handling processes on the 35 | /// system. 36 | class Process 37 | { 38 | public: 39 | /// Type alias for the raw process handle type on the platform. 40 | using raw_handle = ::pid_t; 41 | 42 | static constexpr raw_handle Invalid = -1; 43 | 44 | struct SpawnOptions 45 | { 46 | std::string Program; 47 | std::vector Arguments; 48 | std::map> Environment; 49 | 50 | /// Whether to create a pseudoterminal device when creating the process. 51 | bool CreatePTY = false; 52 | /// Override the standard streams of the spawned process to the 53 | /// file descriptors specified. If \p fd::Invalid is given, the potentially 54 | /// inherited standard stream will be closed. 55 | /// 56 | /// This option has no effect if \p CreatePTY is \p true. 57 | std::optional StandardInput, StandardOutput, StandardError; 58 | }; 59 | 60 | raw_handle raw() const noexcept { return Handle; } 61 | bool hasPty() const noexcept { return PTY.has_value(); } 62 | Pty* getPty() noexcept { return hasPty() ? &*PTY : nullptr; } 63 | 64 | /// \returns Checks if the process had died, and if so, returns \p true. 65 | /// 66 | /// \note This call does not block. If the process died, the operating system 67 | /// \b MAY remove associated information at the invocation of this call. 68 | bool reapIfDead(); 69 | 70 | /// Blocks until the current process instance has terminated. 71 | void wait(); 72 | 73 | /// \returns whether the child process has been \b OBSERVED to be dead. 74 | bool dead() const noexcept { return Dead; } 75 | 76 | /// \returns the exit code of the process, if it has already terminated. 77 | /// This call is only valid after \p wait() concluded or \p reapIfDead() 78 | /// returns \p true. 79 | int exitCode() const noexcept 80 | { 81 | assert(Dead && "Process still alive, exit code is not meaningful!"); 82 | return ExitCode; 83 | } 84 | 85 | /// Send the \p Signal to the underlying process. 86 | void signal(int Signal); 87 | 88 | private: 89 | raw_handle Handle = Invalid; 90 | bool Dead = false; 91 | int ExitCode = 0; 92 | /// The \p Pty assocaited with the process, if \p SpawnOptions::CreatePTY was 93 | /// true. 94 | std::optional PTY; 95 | 96 | public: 97 | /// \returns the PID handle of the currently executing process. 98 | static raw_handle thisProcess(); 99 | 100 | /// \returns the address of the currently executing binary, queried from the 101 | /// kernel. 102 | static std::string thisProcessPath(); 103 | 104 | /// Sends the \p Signal to the process identified by \p PID. 105 | static void signal(raw_handle Handle, int Signal); 106 | 107 | /// Replaces the current process (as if by calling the \p exec() family) in 108 | /// the system with the started one. This is a low-level operation that 109 | /// performs no additional meaningful setup of process state. 110 | /// 111 | /// \warning This command does \b NOT \p fork()! 112 | [[noreturn]] static void exec(const SpawnOptions& Opts); 113 | 114 | /// Spawns a new process based on the specified \p Opts. This process calls 115 | /// \p fork() internally, and then does an \p exec(). The spawned subprocess 116 | /// will be meaningfully set up to be a clearly spawned process. 117 | /// 118 | /// The spawned process will be the child of the current process. The call 119 | /// returns the PID of the child, and execution resumes normally in the 120 | /// parent. 121 | /// 122 | /// \note This call does \b NOT return in the child! 123 | static Process spawn(const SpawnOptions& Opts); 124 | 125 | /// \p fork(): Ask the kernel to create an exact duplicate of the current 126 | /// process. The specified callbacks \p ParentAction and \p ChildAction will 127 | /// be run in the parent and the child process, respectively. 128 | /// 129 | /// \note Execution continues normally after the callbacks retur return! 130 | template 131 | static void fork(ParentFn ParentAction, ChildFn ChildAction) 132 | { 133 | auto ForkResult = CheckedPOSIXThrow([] { return ::fork(); }, "fork()", -1); 134 | if (ForkResult == 0) 135 | ChildAction(); 136 | else 137 | ParentAction(); 138 | } 139 | }; 140 | 141 | using raw_pid = Process::raw_handle; 142 | 143 | } // namespace monomux 144 | -------------------------------------------------------------------------------- /include/core/monomux/system/Pty.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | 22 | #include "monomux/adt/UniqueScalar.hpp" 23 | #include "monomux/system/Pipe.hpp" 24 | #include "monomux/system/fd.hpp" 25 | 26 | namespace monomux 27 | { 28 | 29 | /// Responsible for wrapping a low-level psuedo terminal teletypewriter (PTTY) 30 | /// interface. A pseudoterminal is an emulation of the ancient technology where 31 | /// physical typewriter and printer machines were connected to computers. 32 | class Pty 33 | { 34 | UniqueScalar IsMaster; 35 | fd Master; 36 | fd Slave; 37 | std::string Name; 38 | 39 | /// A \p Pipe for reading from the other side. Only established \b AFTER 40 | /// setting up either as parent or childside. 41 | std::unique_ptr Read; 42 | /// A \p Pipe for writing to the other side. Only established \b AFTER 43 | /// setting up either as parent or childside. 44 | std::unique_ptr Write; 45 | 46 | public: 47 | /// Creates a new PTY-pair. 48 | Pty(); 49 | 50 | /// \returns whether the current instance is open on the master (PTM, control) 51 | /// side. 52 | bool isMaster() const noexcept { return IsMaster; } 53 | 54 | /// \returns whether the current instance is open on the slave (PTS, process) 55 | /// side. 56 | bool isSlave() const noexcept { return !IsMaster; } 57 | 58 | /// \returns the raw file descriptor for the \p Pty side that is currently 59 | /// open. 60 | fd& raw() noexcept { return isMaster() ? Master : Slave; } 61 | 62 | /// \returns the name of the PTY interface that was created (e.g. /dev/pts/2). 63 | const std::string& name() const noexcept { return Name; } 64 | 65 | /// Returns the \p Pipe that can read from the standard output of the other 66 | /// end of the PTY. 67 | /// 68 | /// \note Using this method is only valid once the PTY has established its 69 | /// master/slave status. 70 | Pipe& reader() noexcept 71 | { 72 | assert(Read); 73 | return *Read; 74 | } 75 | /// Returns the \p Pipe that can write data into the standard input of the 76 | /// other end of the PTY. 77 | /// 78 | /// \note Using this method is only valid once the PTY has established its 79 | /// master/slave status. 80 | Pipe& writer() noexcept 81 | { 82 | assert(Write); 83 | return *Write; 84 | } 85 | 86 | /// Executes actions that configure the current PTY from the owning parent's 87 | /// point of view. This usually means that the PTS (pseudoterminal-slave) 88 | /// file descriptor is closed. 89 | void setupParentSide(); 90 | 91 | /// Executes actions that configure the current PTY from a running child 92 | /// process's standpoint, turning it into the controlling terminal of a 93 | /// process. The master file descriptor is also closed. 94 | /// 95 | /// Normally, after a call to this function, it is expected for the child 96 | /// process to be replaced with another one. 97 | void setupChildrenSide(); 98 | 99 | /// Sets the size of the pseudoterminal device to have the given dimensions. 100 | void setSize(unsigned short Rows, unsigned short Columns); 101 | }; 102 | 103 | } // namespace monomux 104 | -------------------------------------------------------------------------------- /include/core/monomux/system/Socket.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | 24 | #include "monomux/adt/UniqueScalar.hpp" 25 | #include "monomux/system/BufferedChannel.hpp" 26 | #include "monomux/system/fd.hpp" 27 | 28 | namespace monomux 29 | { 30 | 31 | /// A socket is a two-way communication channel between a "client" and a 32 | /// "server". 33 | /// 34 | /// This class wraps a Unix domain socket (appearing to applications as a named 35 | /// file in the filesystem) and allows reading or writing to the socket. 36 | /// 37 | /// This implementation uses \p SOCK_STREAM, which is similar to TCP 38 | /// connections: clients connect to the server socket and then a connection is 39 | /// accepted, at which point the connection \e towards that new client becomes 40 | /// its own \p Socket. 41 | /// 42 | /// \note This object does \b NOT manage the set of connected clients or 43 | /// anything related to such notion, only exposes the low-level system calls 44 | /// facilitating socket behaviour. 45 | /// 46 | /// \see socket(7) 47 | class Socket : public BufferedChannel 48 | { 49 | public: 50 | /// Creates a new \p Socket which will be owned by the current instance, and 51 | /// removed on exit. Such sockets can be used to await connections and 52 | /// implement server-like behaviour. 53 | /// 54 | /// If \p InheritInChild is true, the socket will be flagged to be inherited 55 | /// by a potential child process. 56 | /// 57 | /// \see bind(2) 58 | static Socket create(std::string Path, bool InheritInChild = false); 59 | 60 | /// Opens a connection to the socket existing in the file system at \p Path. 61 | /// The connection will be cleaned up during destruction, but the file entity 62 | /// is left intact. A low-level connection is initiated through the socket. 63 | /// Such sockets can be used to implement clients-like behaviour. 64 | /// 65 | /// If \p InheritInChild is true, the socket will be flagged to be inherited 66 | /// by a potential child process. 67 | /// 68 | /// \see connect(2) 69 | static Socket connect(std::string Path, bool InheritInChild = false); 70 | 71 | /// Wraps an already existing file descriptor, \p FD as a socket. 72 | /// Ownership of the resource itself is taken by the \p Socket instance and 73 | /// the file will be closed during destruction, but no additional cleanup 74 | /// may take place. 75 | /// 76 | /// \param Identifier An identifier to assign to the \p Socket. If empty, a 77 | /// default value will be created. 78 | /// 79 | /// \note This method does \b NOT verify whether the wrapped file descriptor 80 | /// is indeed a socket, and assumes it is set up (either in server, or client 81 | /// mode) already. 82 | static Socket wrap(fd&& FD, std::string Identifier); 83 | 84 | /// Starts listening for incoming connection on the current socket by calling 85 | /// \p listen(). This is only valid if the current socket was created in full 86 | /// ownership mode, with the \p create() method. 87 | /// 88 | /// \see listen(2) 89 | void listen(std::size_t QueueSize); 90 | 91 | /// Accepts a new connection on the current serving socket. This operation 92 | /// \b MAY block. This call is only valid if the current socket was created in 93 | /// full ownership mode, and \p listen() had already been called for it. 94 | /// 95 | /// \param Error If non-null and the accepting of the client fails, the error 96 | /// code is returned in this parameter. 97 | /// \param Recoverable If non-null and the accepting of the client fails, but 98 | /// the low-level error is indicative of a potential to try again, will be set 99 | /// to \p true. 100 | /// 101 | /// \see accept(2) 102 | std::optional accept(std::error_code* Error = nullptr, 103 | bool* Recoverable = nullptr); 104 | 105 | ~Socket() noexcept override; 106 | Socket(Socket&&) noexcept = default; 107 | Socket& operator=(Socket&&) noexcept = default; 108 | 109 | using BufferedChannel::read; 110 | using BufferedChannel::write; 111 | 112 | std::size_t optimalReadSize() const noexcept override; 113 | std::size_t optimalWriteSize() const noexcept override; 114 | 115 | protected: 116 | Socket(fd Handle, std::string Identifier, bool NeedsCleanup); 117 | 118 | std::string readImpl(std::size_t Bytes, bool& Continue) override; 119 | std::size_t writeImpl(std::string_view Buffer, bool& Continue) override; 120 | 121 | private: 122 | /// Whether the current instance is \e owning a socket, i.e. controlling it 123 | /// as a server. 124 | UniqueScalar Owning; 125 | /// Whether the current instance is \e listening for incoming connections 126 | /// via \p listen(). 127 | UniqueScalar Listening; 128 | }; 129 | 130 | } // namespace monomux 131 | -------------------------------------------------------------------------------- /include/core/monomux/system/Time.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace monomux 27 | { 28 | 29 | /// Formats the given \p Chrono \p Time object to an internationally viable 30 | /// representation. 31 | template std::string formatTime(const T& Time) 32 | { 33 | std::time_t RawTime = T::clock::to_time_t(Time); 34 | std::tm SplitTime = *std::localtime(&RawTime); 35 | 36 | std::ostringstream Buf; 37 | // Mirror the behaviour of tmux/byobu menu. 38 | Buf << std::put_time(&SplitTime, "%a %b %e %H:%M:%S %Y"); 39 | return Buf.str(); 40 | } 41 | 42 | } // namespace monomux 43 | -------------------------------------------------------------------------------- /include/core/monomux/system/fd.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | 22 | #include 23 | 24 | namespace monomux 25 | { 26 | 27 | /// This is a smart file descriptor wrapper which will call \p close() on the 28 | /// underyling resource at the end of its life. 29 | class fd // NOLINT(readability-identifier-naming) 30 | { 31 | public: 32 | /// The type used by system calls dealing with flags. 33 | using flag_t = decltype(O_RDONLY); 34 | /// The file descriptor type on the system. 35 | using raw_fd = decltype(::open("", 0)); 36 | 37 | static constexpr raw_fd Invalid = -1; 38 | 39 | /// Creates an empty file descriptor that does not wrap anything. 40 | fd() noexcept : Handle(Invalid) {} 41 | 42 | /// Wrap the raw platform resource handle into the RAII object. 43 | fd(raw_fd Handle) noexcept; 44 | 45 | /// Duplicates the file descriptor \p Handle and wraps it into the RAII 46 | /// object. 47 | /// 48 | /// \see dup(2) 49 | static fd dup(raw_fd Handle); 50 | 51 | fd(fd&& RHS) noexcept : Handle(RHS.release()) {} 52 | fd& operator=(fd&& RHS) noexcept 53 | { 54 | if (this == &RHS) 55 | return *this; 56 | Handle = RHS.release(); 57 | return *this; 58 | } 59 | 60 | /// When the wrapper dies, if the object owned a file descriptor, close it. 61 | ~fd() noexcept 62 | { 63 | if (!has()) 64 | return; 65 | close(release()); 66 | } 67 | 68 | bool has() const noexcept { return Handle != Invalid; } 69 | 70 | /// Convert to the system primitive type. 71 | operator raw_fd() const noexcept { return Handle; } 72 | 73 | /// Convert to the system primitive type. 74 | raw_fd get() const noexcept { return Handle; } 75 | 76 | /// Takes the file descriptor from the current object and changes it to not 77 | /// manage anything. 78 | [[nodiscard]] raw_fd release() noexcept 79 | { 80 | raw_fd R = Handle; 81 | Handle = Invalid; 82 | return R; 83 | } 84 | 85 | private: 86 | raw_fd Handle; 87 | 88 | public: 89 | /// \returns the number of file descriptors that the current process may have 90 | /// open, as set by \p ulimit. 91 | static std::size_t maxNumFDs(); 92 | 93 | /// Returns the file descriptor for the standard C I/O object. 94 | static raw_fd fileno(std::FILE* File); 95 | 96 | /// Closes a \b raw file descriptor. 97 | static void close(raw_fd FD) noexcept; 98 | 99 | /// Adds the given \p Flag, from \p fcntl() flags, to the flags of the given 100 | /// file descriptor \p FD. 101 | static void addStatusFlag(raw_fd FD, flag_t Flag) noexcept; 102 | /// Removes the given \p Flag, from \p fcntl() flags, from the flags of the 103 | /// given file descriptor \p FD. 104 | static void removeStatusFlag(raw_fd FD, flag_t Flag) noexcept; 105 | 106 | /// Adds the given \p Flag, from \p fcntl() flags, to the flags of the given 107 | /// file descriptor \p FD. 108 | static void addDescriptorFlag(raw_fd FD, flag_t Flag) noexcept; 109 | /// Removes the given \p Flag, from \p fcntl() flags, from the flags of the 110 | /// given file descriptor \p FD. 111 | static void removeDescriptorFlag(raw_fd FD, flag_t Flag) noexcept; 112 | 113 | /// Shortcut function that sets \p O_NONBLOCK and \p FD_CLOEXEC on a file. 114 | /// This results in the file set to not block when reading from, and to not 115 | /// be inherited by child processes in a \p fork() - \p exec() situation. 116 | static void setNonBlockingCloseOnExec(raw_fd FD) noexcept; 117 | }; 118 | 119 | using raw_fd = fd::raw_fd; 120 | 121 | } // namespace monomux 122 | -------------------------------------------------------------------------------- /include/core/monomux/unreachable.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | 22 | /// If executed during runtime, kills the program and prints the specified 23 | /// message to the standard error stream. 24 | [[noreturn]] void 25 | // NOLINTNEXTLINE(readability-identifier-naming) 26 | unreachable_impl(const char* Msg = nullptr, 27 | const char* File = nullptr, 28 | std::size_t LineNo = 0); 29 | 30 | #ifndef NDEBUG 31 | #define unreachable(MSG) ::unreachable_impl(MSG, __FILE__, __LINE__) 32 | #else 33 | #define unreachable(MSG) ::unreachable_impl(MSG, nullptr, 0) 34 | #endif 35 | -------------------------------------------------------------------------------- /include/implementation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | install(DIRECTORY "monomux" 2 | TYPE INCLUDE 3 | COMPONENT "${MONOMUX_IMPLEMENTATION_LIBRARY_DEV_NAME}") 4 | -------------------------------------------------------------------------------- /include/implementation/monomux/client/Main.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | 24 | #include "monomux/client/Client.hpp" 25 | #include "monomux/system/Environment.hpp" 26 | #include "monomux/system/Process.hpp" 27 | 28 | namespace monomux::client 29 | { 30 | 31 | /// Options interested to invocation of a Monomux Client. 32 | struct Options 33 | { 34 | /// Format the options back into the CLI invocation they were parsed from. 35 | std::vector toArgv() const; 36 | 37 | // (To initialise the bitfields...) 38 | Options(); 39 | 40 | /// \returns if control-mode flags (transmitted to the server through a 41 | /// non-terminal client) are enabled. 42 | bool isControlMode() const noexcept; 43 | 44 | /// Whether the client mode was enabled. 45 | bool ClientMode : 1; 46 | 47 | /// Whether the user requested only listing the sessions on the server, but 48 | /// no connection to be made. 49 | bool OnlyListSessions : 1; 50 | 51 | /// Whether the client should start with showing the session selection menu, 52 | /// and disregard normal startup decision heuristics. 53 | bool InteractiveSessionMenu : 1; 54 | 55 | /// Whether it was requested to detach the latest client from a session. 56 | /// 57 | /// \note This is a control-mode flag. 58 | bool DetachRequestLatest : 1; 59 | 60 | /// Whether it was requested to detach all clients from a session. 61 | /// 62 | /// \note This is a control-mode flag. 63 | bool DetachRequestAll : 1; 64 | 65 | /// Whether it was requested to gather statistics from the running server. 66 | /// 67 | /// \note This is a control-mode flag. 68 | bool StatisticsRequest : 1; 69 | 70 | /// The path to the server socket where the client should connect to. 71 | std::optional SocketPath; 72 | 73 | /// The name of the session the client should create if does not exist, or 74 | /// attach to if exists. 75 | std::optional SessionName; 76 | 77 | /// The options of the programs to start if a new session is created during 78 | /// the client's connection. (Ignored if the client attaches to an existing 79 | /// session.) 80 | std::optional Program; 81 | 82 | /// Contains the master connection to the server, if such was established. 83 | std::optional Connection; 84 | 85 | /// If the client has been invoked within a session and we were able to deduce 86 | /// this, store the session data. 87 | std::optional SessionData; 88 | }; 89 | 90 | /// Attempt to establish connection to a Monomux Server specified in \p Opts. 91 | /// 92 | /// \param Block Whether to continue retrying the connection and block until 93 | /// success. 94 | /// \param FailureReason If given, after an unsuccessful connection, a 95 | /// human-readable reason for the failure will be written to. 96 | std::optional 97 | connect(Options& Opts, bool Block, std::string* FailureReason); 98 | 99 | /// Attempts to make the \p Client fully featured with a \b Data connection, 100 | /// capable of actually exchanging user-specific information with the server. 101 | /// 102 | /// \param FailureReason If given, after an unsuccessful connection, a 103 | /// human-readable reason for the failure will be written to. 104 | bool makeWholeWithData(Client& Client, std::string* FailureReason); 105 | 106 | /// Executes the Monomux Client logic. 107 | /// 108 | /// \returns \p ExitCode 109 | int main(Options& Opts); 110 | 111 | } // namespace monomux::client 112 | -------------------------------------------------------------------------------- /include/implementation/monomux/client/Terminal.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | 22 | #include "monomux/adt/Atomic.hpp" 23 | #include "monomux/adt/POD.hpp" 24 | #include "monomux/adt/UniqueScalar.hpp" 25 | #include "monomux/system/Pipe.hpp" 26 | 27 | namespace monomux::client 28 | { 29 | 30 | class Client; 31 | 32 | class Terminal 33 | { 34 | public: 35 | /// A record containing the size information of the controlled terminal. 36 | struct Size 37 | { 38 | unsigned short Rows; 39 | unsigned short Columns; 40 | }; 41 | 42 | Terminal(raw_fd InputStream, raw_fd OutputStream); 43 | 44 | /// Engages control over the current input and ouput terminal and sets it 45 | /// into the mode necesary for remote communication. 46 | void engage(); 47 | 48 | bool engaged() const noexcept { return Engaged; } 49 | 50 | /// Disengage control over the current input and output terminal, resetting 51 | /// the default state. 52 | void disengage(); 53 | 54 | /// Sets the current \p Terminal to be the terminal associated with \p Client. 55 | /// Data typed into the \p input() of this terminal will be considered input 56 | /// by the client, and data received by the client will be printed to 57 | /// \p output(). 58 | void setupClient(Client& Client); 59 | 60 | Client* getClient() noexcept { return AssociatedClient; } 61 | const Client* getClient() const noexcept { return AssociatedClient; } 62 | 63 | /// Releases the associated client and turns off its callbacks from firing 64 | /// the handlers of the \p Terminal. 65 | /// 66 | /// \see setupClient 67 | void releaseClient(); 68 | 69 | Pipe* input() noexcept { return In.get(); } 70 | const Pipe* input() const noexcept { return In.get(); } 71 | Pipe* output() noexcept { return Out.get(); } 72 | const Pipe* output() const noexcept { return Out.get(); } 73 | 74 | Size getSize() const; 75 | void notifySizeChanged() const noexcept; 76 | 77 | private: 78 | std::unique_ptr In; 79 | std::unique_ptr Out; 80 | UniqueScalar AssociatedClient; 81 | UniqueScalar Engaged; 82 | POD OriginalTerminalSettings; 83 | 84 | /// Whether a signal interrupt indicated that the window size of the client 85 | /// had changed. 86 | mutable Atomic WindowSizeChanged; 87 | 88 | #ifndef NDEBUG 89 | /// The handler callbacks in the client receive a \p Terminal instance's 90 | /// pointer bound. If the object is moved from, the moved-from will reset 91 | /// this value to \p false, with which we can track the access of an invalid 92 | /// object with sanitisers. 93 | UniqueScalar MovedFromCheck; 94 | #endif 95 | 96 | /// Callback function fired when the client reports available input. 97 | static void clientInput(Terminal* Term, Client& Client); 98 | /// Callback function fired when the client reports available output. 99 | static void clientOutput(Terminal* Term, Client& Client); 100 | /// Callback function fired when the client is ready to process events of the 101 | /// environment. 102 | static void clientEventReady(Terminal* Term, Client& Client); 103 | }; 104 | 105 | } // namespace monomux::client 106 | -------------------------------------------------------------------------------- /include/implementation/monomux/server/Main.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | 24 | namespace monomux::server 25 | { 26 | 27 | /// Options interested to invocation of a Monomux Server. 28 | struct Options 29 | { 30 | /// Format the options back into the CLI invocation they were parsed from. 31 | std::vector toArgv() const; 32 | 33 | // (To initialise the bitfields...) 34 | Options(); 35 | 36 | /// Whether the server mode was enabled. 37 | bool ServerMode : 1; 38 | 39 | /// Whether the server should run as a background process. 40 | bool Background : 1; 41 | 42 | /// Whether the server should automatically quit if the last session running 43 | /// has terminated. 44 | bool ExitOnLastSessionTerminate : 1; 45 | 46 | /// The path of the server socket to start listening on. 47 | std::optional SocketPath; 48 | }; 49 | 50 | /// \p exec() into a server process that is created with the \p Opts options. 51 | [[noreturn]] void exec(const Options& Opts, const char* ArgV0); 52 | 53 | /// Executes the Monomux Server logic. 54 | /// 55 | /// \returns \p ExitCode 56 | int main(Options& Opts); 57 | 58 | } // namespace monomux::server 59 | -------------------------------------------------------------------------------- /include/implementation/monomux/system/Crash.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace monomux 29 | { 30 | 31 | /// Handler for formatting a raw "Segmentation fault" or "Aborted" crash message 32 | /// into something meaningful that aids with debugging. 33 | class Backtrace 34 | { 35 | public: 36 | /// Contains the results of the \p backtrace() operation as raw sub-strings 37 | /// into the runtime's output. 38 | /// 39 | /// \note These views are valid only if the \p SymbolDataBuffer is valid. 40 | struct RawData 41 | { 42 | /// The symbolic representation of each address consists of the function 43 | /// name (if this can be determined), a hexademical offset into the 44 | /// function, and the actual return address (in hexadecimal). 45 | std::string_view Full; 46 | 47 | /// The hexadecimal address string of the instruction in the loaded binary. 48 | std::string_view HexAddress; 49 | /// The name of the binary the symbol is loaded from. 50 | std::string_view Binary; 51 | /// The name of the symbol. 52 | std::string_view Symbol; 53 | /// The hexadecimal offset from the symbol label (or if there is no symbol, 54 | /// from the start of the image) for the instruction of the stack frame. 55 | std::string_view Offset; 56 | }; 57 | 58 | struct Symbol 59 | { 60 | /// The symbol name associated with the frame, as returned by a symboliser. 61 | std::string Name; 62 | /// The location extracted from the executing image where the frame's 63 | /// executed instruction is compiled from. Requires the existence of debug 64 | /// information. 65 | std::string Filename; 66 | std::size_t Line, Column; 67 | 68 | /// Symbols might be inlined into each other if an optimised build is 69 | /// set up. If such is true for \p this then this field will contain the 70 | /// data for the inlining, potentially recursively. 71 | std::unique_ptr InlinedBy; 72 | 73 | /// Merges symbol information in a sensible fashion from another symboliser 74 | /// result. 75 | void mergeFrom(Symbol&& RHS); 76 | 77 | /// Creates the inlining symbol's instance and returns it for data filling. 78 | Symbol& startInlineInfo(); 79 | 80 | /// \returns whether the Symbol instance, or anything in the \p InlinedBy 81 | /// chain conveys meaningful information. 82 | bool hasMeaningfulInformation() const; 83 | }; 84 | 85 | struct Frame 86 | { 87 | /// The index of the stack frame. This is decremented, so the latest stack 88 | /// frame has the highest index. 89 | std::size_t Index; 90 | 91 | /// Each item in the array pointed to by \p buffer is of type \p void*, and 92 | /// is the return address from the corresponding stack frame. 93 | const void* Address; 94 | 95 | /// The offset in the raw image (as opposed to its memory-loaded version) 96 | /// for the symbol's address. This value is used during symbolisation to 97 | /// find the appropriate symbol data. 98 | /// 99 | /// \see prettify() 100 | const void* ImageOffset; 101 | 102 | /// Contains the raw data returned by the runtime about the machine-level 103 | /// symbol information. 104 | RawData Data; 105 | 106 | /// The location of the executed instruction, in the source code, if such 107 | /// information was available. This is conditional on having a debug-like 108 | /// build, access to the source files in the debug information, and a 109 | /// symboliser (e.g., \p addr2line) capable of retrieving these details. 110 | std::optional Info; 111 | }; 112 | 113 | /// The maximum size supported for generating a backtrace. Larger \p Depth 114 | /// values will be truncated to this value. 115 | static constexpr std::size_t MaxSize = 512; 116 | 117 | /// Creates a backtrace from the current function with at most \p Depth 118 | /// stack \p Frame visited. The first \p Ignored stack frames will be 119 | /// skipped from the report. 120 | Backtrace(std::size_t Depth = MaxSize, std::size_t Ignore = 0); 121 | 122 | ~Backtrace(); 123 | 124 | /// \returns the stack frames created and stored when the \p Backtrace 125 | /// instance was constructed. These frames are allocated in the usual order, 126 | /// with the most recent stack frame being the first (index 0) in the vector. 127 | const std::vector& getFrames() const noexcept { return Frames; } 128 | 129 | /// Prettify the stack symbol information and fill \p Pretty for each \p Frame 130 | /// by calling system binaries such as \p addr2line on the collected raw data. 131 | void prettify(); 132 | 133 | const std::size_t IgnoredFrameCount; 134 | 135 | private: 136 | std::vector Frames; 137 | 138 | /// The buffer where the backtrace generator returns the symbol information 139 | /// in a string format. 140 | const char* const* SymbolDataBuffer; 141 | }; 142 | 143 | /// Prints \p Trace to the output \p OS using the default formatting logic. 144 | void printBacktrace(std::ostream& OS, const Backtrace& Trace); 145 | 146 | /// Generate a backtrace right now, and print it to \p OS with the default 147 | /// formatting logic. 148 | void printBacktrace(std::ostream& OS, bool Prettify = true); 149 | 150 | } // namespace monomux 151 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories( 2 | ${CMAKE_CURRENT_SOURCE_DIR} 3 | ) 4 | 5 | # Create some variables to store files needed for distributing Monomux's core as 6 | # a reusable library. 7 | set(libmonomuxCore_SOURCES 8 | ${CMAKE_CURRENT_SOURCE_DIR}/Log.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/unreachable.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp 11 | ) 12 | 13 | # Create some variables to store filenames needed to package the reference 14 | # implementation of Monomux as a reusable library. 15 | set(libmonomuxImplementation_SOURCES 16 | ${CMAKE_CURRENT_SOURCE_DIR}/Config.cpp 17 | ) 18 | 19 | # Create variables to store the filenames that are only needed for the 20 | # user-facing entry point. 21 | set(monomux_SOURCES 22 | ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp 23 | ) 24 | 25 | add_subdirectory(client) 26 | add_subdirectory(control) 27 | add_subdirectory(server) 28 | add_subdirectory(system) 29 | 30 | if (NOT MONOMUX_LIBRARY_TYPE STREQUAL "UNITY") 31 | # monomuxCore is the library that contains the freely embeddable subset of 32 | # Monomux that expose a reusable API. This should be safe to embed because 33 | # everything done and created by the library is owned by the library instances. 34 | add_library(monomuxCore ${MONOMUX_LIBRARY_TYPE} 35 | ${libmonomuxCore_SOURCES} 36 | ) 37 | add_dependencies(monomuxCore 38 | monomux_generate_version_h) 39 | target_link_libraries(monomuxCore PUBLIC 40 | util 41 | ) 42 | 43 | # monomuxMain contains the implementation of a capable Server and Client built 44 | # on top of monomuxCore. This library contains additional tools that might not 45 | # be completely safe to embed in another application, because direct control of 46 | # application state (signals, terminal, etc.) is done here. 47 | add_library(monomuxImplementation ${MONOMUX_LIBRARY_TYPE} 48 | ${libmonomuxImplementation_SOURCES} 49 | ) 50 | target_link_libraries(monomuxImplementation PUBLIC 51 | monomuxCore 52 | dl 53 | util 54 | ) 55 | 56 | # The monomux binary adds the user-facing command-line parset to the mix, 57 | # creating a proper executable. 58 | add_executable(monomux 59 | ${monomux_SOURCES} 60 | ) 61 | target_link_libraries(monomux PUBLIC 62 | monomuxImplementation 63 | ) 64 | else() 65 | # In Unity build, we'll create a single output executable comprised of all 66 | # the source files that would have been part of separate libraries. 67 | add_executable(monomux 68 | ${libmonomuxCore_SOURCES} 69 | ${libmonomuxImplementation_SOURCES} 70 | ${monomux_SOURCES} 71 | ) 72 | add_dependencies(monomux 73 | monomux_generate_version_h) 74 | target_link_libraries(monomux PUBLIC 75 | dl 76 | util 77 | ) 78 | endif() 79 | 80 | set_target_properties(monomux PROPERTIES 81 | # Put resulting binary to /, not /src/... 82 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" 83 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" 84 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" 85 | ) 86 | 87 | if (NOT MONOMUX_LIBRARY_TYPE STREQUAL "UNITY") 88 | install(TARGETS monomuxCore 89 | COMPONENT "${MONOMUX_CORE_LIBRARY_DEV_NAME}" 90 | ) 91 | install(TARGETS monomuxImplementation 92 | COMPONENT "${MONOMUX_IMPLEMENTATION_LIBRARY_DEV_NAME}" 93 | ) 94 | endif() 95 | 96 | if (NOT MONOMUX_LIBRARY_TYPE STREQUAL "SHARED") 97 | # If we are using static libs or unity build, the main target only needs the 98 | # binary that got the static library linked in. 99 | install(TARGETS monomux 100 | COMPONENT "${MONOMUX_NAME}" 101 | ) 102 | else() 103 | # If we are using SHARED libs, the main install target needs the shared libs 104 | # too! 105 | install(TARGETS monomux monomuxCore monomuxImplementation 106 | COMPONENT "${MONOMUX_NAME}") 107 | endif() 108 | -------------------------------------------------------------------------------- /src/Config.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | #include 21 | 22 | #include "Config.hpp" 23 | 24 | namespace monomux 25 | { 26 | 27 | std::string getHumanReadableConfiguration() 28 | { 29 | std::ostringstream Buf; 30 | 31 | Buf << " * " << MONOMUX_BUILD_TYPE << " build\n"; 32 | 33 | #ifdef MONOMUX_BUILD_SHARED_LIBS 34 | Buf << " * SHARED (dynamic) library\n"; 35 | #else /* !MONOMUX_BUILD_SHARED_LIBS */ 36 | #ifdef MONOMUX_BUILD_UNITY 37 | Buf << " * UNITY library\n"; 38 | #else /* !MONOMUX_BUILD_UNITY */ 39 | Buf << " * STATIC library\n"; 40 | #endif /* MONOMUX_BUILD_UNITY */ 41 | #endif /* MONOMUX_BUILD_SHARED_LIBS */ 42 | 43 | #ifndef MONOMUX_NON_ESSENTIAL_LOGS 44 | Buf << " - Non-essential trace logs\n"; 45 | #else /* !MONOMUX_NON_ESSENTIAL_LOGS */ 46 | Buf << " + Non-essential trace logs\n"; 47 | #endif /* MONOMUX_NON_ESSENTIAL_LOGS */ 48 | 49 | std::string S = Buf.str(); 50 | 51 | { 52 | // Clean up multiple subsequent newlines from the output. 53 | static constexpr std::string_view DoubleNewline = "\n\n"; 54 | static constexpr std::string_view SingleNewline = "\n"; 55 | std::string::size_type P = 0; 56 | while ((P = S.find(DoubleNewline.data())) != std::string::npos) 57 | S.replace( 58 | P, DoubleNewline.size(), SingleNewline.data(), SingleNewline.size()); 59 | } 60 | 61 | return S; 62 | } 63 | 64 | } // namespace monomux 65 | -------------------------------------------------------------------------------- /src/Config.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | #include 21 | 22 | #include "monomux/Config.h" 23 | 24 | namespace monomux 25 | { 26 | 27 | /// \returns details about the configuration of the current Monomux build in a 28 | /// human-readable format. 29 | std::string getHumanReadableConfiguration(); 30 | 31 | } // namespace monomux 32 | -------------------------------------------------------------------------------- /src/Config.in.h: -------------------------------------------------------------------------------- 1 | #ifndef MONOMUX_CONFIG_H 2 | #define MONOMUX_CONFIG_H 3 | 4 | /* This file contains the CMake-level configuration variables that are exposed 5 | * to the compilers executed. 6 | */ 7 | 8 | /* If set, the built binaries will be composed of several shared library (.so, 9 | * .dll) for reusability in other projects. 10 | * 11 | * Turn off to improve optimisations if Monomux is only used as the reference 12 | * implementation tool (normally it is). 13 | */ 14 | #cmakedefine MONOMUX_BUILD_SHARED_LIBS 15 | 16 | /* If set, the built binary will be created from a single translation unit of 17 | * the entire project. 18 | * 19 | * This allows for greater optimisations to be performed by the compiler at the 20 | * expence of a substantial drag on compiler performance. 21 | */ 22 | #cmakedefine MONOMUX_BUILD_UNITY 23 | 24 | /* If set, the built binary will contain some additional log outputs that are 25 | * needed for verbose debugging of the project. 26 | * 27 | * Turn off to cut down further on the binary size for production. 28 | */ 29 | #cmakedefine MONOMUX_NON_ESSENTIAL_LOGS 30 | 31 | /* The build type for the current build. */ 32 | #define MONOMUX_BUILD_TYPE "${CMAKE_BUILD_TYPE}" 33 | 34 | #endif /* MONOMUX_CONFIG_H */ 35 | -------------------------------------------------------------------------------- /src/ExitCode.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #pragma once 20 | 21 | namespace monomux 22 | { 23 | 24 | /// Contains the exit codes MonoMux \p main() functions return with. 25 | enum ExitCode : int 26 | { 27 | /// Successful execution (processes exited gracefully). 28 | EXIT_Success = 0, 29 | 30 | /// Indicates a fatal error in the communication with the server. 31 | EXIT_SystemError = 1, 32 | 33 | /// Values specified on the command-line of MonoMux are erroneous. 34 | EXIT_InvocationError = 2, 35 | 36 | /// Nonspecific other failure. 37 | EXIT_Failure = 3, 38 | }; 39 | 40 | } // namespace monomux 41 | -------------------------------------------------------------------------------- /src/Log.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | 21 | #include "monomux/system/Time.hpp" 22 | 23 | #include "monomux/Log.hpp" 24 | 25 | namespace monomux::log 26 | { 27 | 28 | // clang-format off 29 | static constexpr const char* SeverityName[Min + 1] = {" ", 30 | "!!! FATAL ", 31 | " !! ERROR ", 32 | " ! Warning", 33 | " Info ", 34 | " > Debug ", 35 | " >> trace ", 36 | ">>> data "}; 37 | static constexpr const char InvalidSeverity[] = "??? Invalid"; 38 | // clang-format on 39 | 40 | const char* Logger::levelName(Severity S) noexcept 41 | { 42 | if (S > log::Min || S < log::Max) 43 | { 44 | assert(false && "Invalid severity to stringify!"); 45 | return InvalidSeverity; 46 | } 47 | return SeverityName[S]; 48 | } 49 | 50 | Logger::OutputBuffer::OutputBuffer(std::ostream& OS, 51 | bool Discard, 52 | std::string_view Prefix) 53 | : Discard(Discard), OS(&OS) 54 | { 55 | if (!Discard) 56 | Buffer << Prefix; 57 | } 58 | 59 | Logger::OutputBuffer::~OutputBuffer() noexcept(false) 60 | { 61 | if (!Discard) 62 | (*OS) << Buffer.str() << std::endl; 63 | } 64 | 65 | std::unique_ptr Logger::Singleton; 66 | 67 | Logger& Logger::get() 68 | { 69 | if (!Singleton) 70 | { 71 | Singleton = std::make_unique(Default, std::clog); 72 | MONOMUX_TRACE_LOG(Singleton->operator()(log::Debug, "logger") 73 | << "Initialised at address " << Singleton.get()); 74 | } 75 | 76 | return *Singleton; 77 | } 78 | 79 | Logger* Logger::tryGet() { return Singleton.get(); } 80 | 81 | std::size_t Logger::digits(std::size_t Number) 82 | { 83 | std::size_t R = 1; 84 | while (Number > 0) 85 | { 86 | Number /= 10; // NOLINT(readability-magic-numbers) 87 | ++R; 88 | } 89 | return R; 90 | } 91 | 92 | Logger::Logger(Severity S, std::ostream& OS) : SeverityLimit(S), OS(&OS) {} 93 | 94 | Logger::OutputBuffer Logger::operator()(Severity S, std::string_view Facility) 95 | { 96 | std::ostringstream LogPrefix; 97 | bool Discarding = S > getLimit(); 98 | if (!Discarding) 99 | { 100 | LogPrefix << '[' << formatTime(std::chrono::system_clock::now()) << ']'; 101 | if (std::string_view SN = SeverityName[S]; !SN.empty()) 102 | LogPrefix << '[' << SN << "] "; 103 | if (!Facility.empty()) 104 | LogPrefix << Facility << ": "; 105 | else 106 | LogPrefix << "?: "; 107 | } 108 | return OutputBuffer{*OS, Discarding, LogPrefix.str()}; 109 | } 110 | 111 | } // namespace monomux::log 112 | -------------------------------------------------------------------------------- /src/Version.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | #include 21 | 22 | #include "monomux/Version.h" 23 | 24 | #include "monomux/Version.hpp" 25 | 26 | namespace monomux 27 | { 28 | 29 | Version getVersion() 30 | { 31 | Version V{}; 32 | V.Major = std::stoull(MONOMUX_VERSION_MAJOR); 33 | V.Minor = std::stoull(MONOMUX_VERSION_MINOR); 34 | V.Patch = std::stoull(MONOMUX_VERSION_PATCH); 35 | V.Build = std::stoull(MONOMUX_VERSION_TWEAK); 36 | V.Offset = 0; 37 | V.IsDirty = false; 38 | 39 | #ifdef MONOMUX_VERSION_HAS_EXTRAS 40 | V.Offset = std::stoull(MONOMUX_VERSION_OFFSET); 41 | V.Commit = MONOMUX_VERSION_COMMIT; 42 | V.IsDirty = std::strlen(MONOMUX_VERSION_DIRTY) > 0; 43 | #endif 44 | 45 | return V; 46 | } 47 | 48 | std::string getShortVersion() 49 | { 50 | std::ostringstream Buf; 51 | Version V = getVersion(); 52 | Buf << V.Major << '.' << V.Minor; 53 | if (V.Patch || V.Build) 54 | Buf << '.' << V.Patch; 55 | if (V.Build) 56 | Buf << '.' << V.Build; 57 | return Buf.str(); 58 | } 59 | 60 | std::string getFullVersion() 61 | { 62 | std::ostringstream Buf; 63 | Version V = getVersion(); 64 | Buf << getShortVersion(); 65 | if (V.Offset || !V.Commit.empty()) 66 | Buf << '+' << V.Offset << '(' << V.Commit << ')'; 67 | if (V.IsDirty) 68 | Buf << "-dirty!"; 69 | return Buf.str(); 70 | } 71 | 72 | } // namespace monomux 73 | -------------------------------------------------------------------------------- /src/Version.in.h: -------------------------------------------------------------------------------- 1 | #ifndef MONOMUX_VERSION_H 2 | #define MONOMUX_VERSION_H 3 | 4 | /* The main version number components. */ 5 | #define MONOMUX_VERSION_MAJOR "${VERSION_MAJOR}" 6 | #define MONOMUX_VERSION_MINOR "${VERSION_MINOR}" 7 | #define MONOMUX_VERSION_PATCH "${VERSION_PATCH}" 8 | 9 | /* The "tweak" version number is a sub-release indicator. 10 | * This is usually a direct build number. 11 | */ 12 | #define MONOMUX_VERSION_TWEAK "${VERSION_TWEAK}" 13 | 14 | /* Whether the versioning system uncovered additional detail. */ 15 | #define MONOMUX_VERSION_HAS_EXTRAS "${VERSION_HAS_EXTRAS}" 16 | 17 | #ifdef MONOMUX_VERSION_HAS_EXTRAS 18 | 19 | /* The amount of commits since the tagged version. */ 20 | #define MONOMUX_VERSION_OFFSET "${VERSION_OFFSET}" 21 | 22 | /* The hash of the current commit. */ 23 | #define MONOMUX_VERSION_COMMIT "${VERSION_COMMIT}" 24 | 25 | /* Whether there were local, uncommitted changes during build. */ 26 | #define MONOMUX_VERSION_DIRTY "${VERSION_DIRTY}" 27 | 28 | #endif /* MONOMUX_VERSION_HAS_EXTRAS */ 29 | 30 | #endif /* MONOMUX_VERSION_H */ 31 | -------------------------------------------------------------------------------- /src/client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND libmonomuxCore_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/Client.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/ControlClient.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/Dispatch.cpp 5 | ) 6 | set(libmonomuxCore_SOURCES "${libmonomuxCore_SOURCES}" PARENT_SCOPE) 7 | 8 | list(APPEND libmonomuxImplementation_SOURCES 9 | ${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/Terminal.cpp 11 | ) 12 | set(libmonomuxImplementation_SOURCES "${libmonomuxImplementation_SOURCES}" PARENT_SCOPE) 13 | -------------------------------------------------------------------------------- /src/client/ControlClient.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "monomux/control/Message.hpp" 20 | #include "monomux/control/PascalString.hpp" 21 | 22 | #include "monomux/client/ControlClient.hpp" 23 | 24 | namespace monomux::client 25 | { 26 | 27 | ControlClient::ControlClient(Client& C) : BackingClient(C) {} 28 | 29 | ControlClient::ControlClient(Client& C, std::string Session) 30 | : BackingClient(C), SessionName(std::move(Session)) 31 | { 32 | BackingClient.requestAttach(SessionName); 33 | } 34 | 35 | void ControlClient::requestDetachLatestClient() 36 | { 37 | using namespace monomux::message; 38 | if (!BackingClient.attached()) 39 | return; 40 | 41 | auto X = BackingClient.inhibitControlResponse(); 42 | sendMessage(BackingClient.getControlSocket(), 43 | request::Detach{request::Detach::Latest}); 44 | receiveMessage(BackingClient.getControlSocket()); 45 | } 46 | 47 | void ControlClient::requestDetachAllClients() 48 | { 49 | using namespace monomux::message; 50 | if (!BackingClient.attached()) 51 | return; 52 | 53 | auto X = BackingClient.inhibitControlResponse(); 54 | sendMessage(BackingClient.getControlSocket(), 55 | request::Detach{request::Detach::All}); 56 | receiveMessage(BackingClient.getControlSocket()); 57 | } 58 | 59 | std::string ControlClient::requestStatistics() 60 | { 61 | using namespace monomux::message; 62 | 63 | auto X = BackingClient.inhibitControlResponse(); 64 | sendMessage(BackingClient.getControlSocket(), request::Statistics{}); 65 | auto Response = 66 | receiveMessage(BackingClient.getControlSocket()); 67 | 68 | if (!Response) 69 | throw std::runtime_error{"Failed to receive a valid response!"}; 70 | return std::move(Response)->Contents; 71 | } 72 | 73 | } // namespace monomux::client 74 | -------------------------------------------------------------------------------- /src/client/Dispatch.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "monomux/Log.hpp" 20 | #include "monomux/client/Client.hpp" 21 | #include "monomux/control/Message.hpp" 22 | #include "monomux/unreachable.hpp" 23 | 24 | #define LOG(SEVERITY) monomux::log::SEVERITY("client/Dispatch") 25 | 26 | using namespace monomux::message; 27 | 28 | namespace monomux::client 29 | { 30 | 31 | void Client::setUpDispatch() 32 | { 33 | #define KIND(E) static_cast(MessageKind::E) 34 | #define MEMBER(NAME) &Client::NAME 35 | #define DISPATCH(K, FUNCTION) registerMessageHandler(KIND(K), MEMBER(FUNCTION)); 36 | #include "monomux/client/Dispatch.ipp" 37 | #undef MEMBER 38 | #undef KIND 39 | } 40 | 41 | #define HANDLER(NAME) \ 42 | void Client::NAME(Client& Client, std::string_view Message) 43 | 44 | #define MSG(TYPE) \ 45 | std::optional Msg = TYPE::decode(Message); \ 46 | if (!Msg) \ 47 | return; \ 48 | MONOMUX_TRACE_LOG(LOG(trace) << __PRETTY_FUNCTION__); 49 | 50 | HANDLER(responseClientID) 51 | { 52 | MSG(response::ClientID); 53 | 54 | Client.ClientID = Msg->Client.ID; 55 | Client.Nonce.emplace(Msg->Client.Nonce); 56 | 57 | MONOMUX_TRACE_LOG(LOG(data) << "Client is \"" << Client.ClientID 58 | << "\" with nonce: " << *Client.Nonce); 59 | } 60 | 61 | HANDLER(receivedDetachNotification) 62 | { 63 | using namespace monomux::message::notification; 64 | MSG(notification::Detached); 65 | 66 | switch (Msg->Mode) 67 | { 68 | case Detached::Detach: 69 | Client.exit(Detached, 0, ""); 70 | break; 71 | case Detached::Exit: 72 | Client.exit(SessionExit, Msg->ExitCode, ""); 73 | break; 74 | case Detached::ServerShutdown: 75 | Client.exit(ServerExit, 0, ""); 76 | break; 77 | case Detached::Kicked: 78 | Client.exit(ServerKicked, 0, std::move(Msg->Reason)); 79 | break; 80 | } 81 | } 82 | 83 | #undef HANDLER 84 | 85 | } // namespace monomux::client 86 | 87 | #undef MSG 88 | #undef HANDLER 89 | #undef LOG 90 | -------------------------------------------------------------------------------- /src/client/Terminal.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include "monomux/client/Client.hpp" 27 | #include "monomux/system/CheckedPOSIX.hpp" 28 | #include "monomux/system/Pipe.hpp" 29 | 30 | #include "monomux/client/Terminal.hpp" 31 | 32 | #include "monomux/Log.hpp" 33 | #define LOG(SEVERITY) monomux::log::SEVERITY("client/Terminal") 34 | 35 | namespace monomux::client 36 | { 37 | 38 | // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) 39 | Terminal::Terminal(raw_fd InputStream, raw_fd OutputStream) 40 | : AssociatedClient(nullptr) 41 | { 42 | #ifndef NDEBUG 43 | MovedFromCheck = true; 44 | #endif 45 | 46 | std::ostringstream InName; 47 | std::ostringstream OutName; 48 | InName << "'; 49 | OutName << "'; 50 | 51 | In = std::make_unique( 52 | Pipe::weakWrap(InputStream, Pipe::Read, InName.str())); 53 | Out = std::make_unique( 54 | Pipe::weakWrap(OutputStream, Pipe::Write, OutName.str())); 55 | } 56 | 57 | void Terminal::engage() 58 | { 59 | if (engaged()) 60 | throw std::logic_error{"Already engaged."}; 61 | 62 | OriginalTerminalSettings.reset(); 63 | CheckedPOSIXThrow( 64 | [this] { return ::tcgetattr(In->raw(), &OriginalTerminalSettings); }, 65 | "tcgetattr(): I/O is not a TTY?", 66 | -1); 67 | 68 | In->setNonblocking(); 69 | 70 | POD NewSettings = OriginalTerminalSettings; 71 | NewSettings->c_iflag &= 72 | ~(IXON | IXOFF | ICRNL | INLCR | IGNCR | IMAXBEL | ISTRIP); 73 | NewSettings->c_iflag |= IGNBRK; 74 | NewSettings->c_oflag &= ~(OPOST | ONLCR | OCRNL | ONLRET); 75 | NewSettings->c_lflag &= ~(IEXTEN | ICANON | ECHO | ECHOE | ECHONL | ECHOCTL | 76 | ECHOPRT | ECHOKE | ISIG); 77 | NewSettings->c_cc[VMIN] = 1; 78 | NewSettings->c_cc[VTIME] = 0; 79 | 80 | CheckedPOSIXThrow( 81 | [this, &NewSettings] { 82 | return ::tcsetattr(In->raw(), TCSANOW, &NewSettings); 83 | }, 84 | "tcsetattr()", 85 | -1); 86 | 87 | // TODO: Clear the screen and implement a redraw request from serverside. 88 | 89 | Engaged = true; 90 | } 91 | 92 | void Terminal::disengage() 93 | { 94 | if (!engaged()) 95 | return; 96 | 97 | In->setBlocking(); 98 | 99 | CheckedPOSIXThrow( 100 | [this] { 101 | return ::tcsetattr(In->raw(), TCSADRAIN, &OriginalTerminalSettings); 102 | }, 103 | "tcsetattr()", 104 | -1); 105 | 106 | // TODO: Clear the screen. 107 | 108 | Engaged = false; 109 | } 110 | 111 | void Terminal::clientInput(Terminal* Term, Client& Client) 112 | { 113 | assert(Term->MovedFromCheck && 114 | "Terminal object registered as callback was moved."); 115 | if (Client.getInputFile() != Term->input()->raw()) 116 | throw std::invalid_argument{"Client InputFD != Terminal input"}; 117 | 118 | do 119 | { 120 | static constexpr std::size_t ReadSize = BUFSIZ; 121 | std::string Input = Term->input()->read(ReadSize); 122 | if (Input.empty()) 123 | return; 124 | 125 | Client.sendData(Input); 126 | } while (Term->input()->hasBufferedRead()); 127 | Term->input()->tryFreeResources(); 128 | } 129 | 130 | void Terminal::clientOutput(Terminal* Term, Client& Client) 131 | { 132 | assert(Term->MovedFromCheck && 133 | "Terminal object registered as callback was moved."); 134 | 135 | static constexpr std::size_t ReadSize = BUFSIZ; 136 | std::string Output = Client.getDataSocket()->read(ReadSize); 137 | Term->output()->write(Output); 138 | 139 | while (Term->output()->hasBufferedWrite()) 140 | Term->output()->flushWrites(); 141 | Term->output()->tryFreeResources(); 142 | } 143 | 144 | void Terminal::clientEventReady(Terminal* Term, Client& Client) 145 | { 146 | assert(Term->MovedFromCheck && 147 | "Terminal object registered as callback was moved."); 148 | 149 | if (Term->WindowSizeChanged.get().load()) 150 | { 151 | Size S = Term->getSize(); 152 | Client.notifyWindowSize(S.Rows, S.Columns); 153 | Term->WindowSizeChanged.get().store(false); 154 | } 155 | } 156 | 157 | void Terminal::setupClient(Client& Client) 158 | { 159 | if (AssociatedClient) 160 | releaseClient(); 161 | 162 | Client.setInputFile(In->raw()); 163 | Client.setInputCallback( 164 | // NOLINTNEXTLINE(modernize-avoid-bind) 165 | std::bind(&Terminal::clientInput, this, std::placeholders::_1)); 166 | Client.setDataCallback( 167 | // NOLINTNEXTLINE(modernize-avoid-bind) 168 | std::bind(&Terminal::clientOutput, this, std::placeholders::_1)); 169 | Client.setExternalEventProcessor( 170 | // NOLINTNEXTLINE(modernize-avoid-bind) 171 | std::bind(&Terminal::clientEventReady, this, std::placeholders::_1)); 172 | 173 | AssociatedClient = &Client; 174 | } 175 | 176 | void Terminal::releaseClient() 177 | { 178 | if (!AssociatedClient) 179 | return; 180 | 181 | AssociatedClient->setDataCallback({}); 182 | AssociatedClient->setInputCallback({}); 183 | AssociatedClient->setExternalEventProcessor({}); 184 | AssociatedClient->setInputFile(fd::Invalid); 185 | 186 | AssociatedClient = nullptr; 187 | } 188 | 189 | Terminal::Size Terminal::getSize() const 190 | { 191 | POD Raw; 192 | CheckedPOSIXThrow( 193 | [this, &Raw] { return ::ioctl(In->raw(), TIOCGWINSZ, &Raw); }, 194 | "ioctl(0, TIOCGWINSZ /* get window size */);", 195 | -1); 196 | Size S; 197 | S.Rows = Raw->ws_row; 198 | S.Columns = Raw->ws_col; 199 | return S; 200 | } 201 | 202 | void Terminal::notifySizeChanged() const noexcept 203 | { 204 | WindowSizeChanged.get().store(true); 205 | } 206 | 207 | } // namespace monomux::client 208 | 209 | #undef LOG 210 | -------------------------------------------------------------------------------- /src/control/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND libmonomuxCore_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/Message.cpp 3 | ) 4 | set(libmonomuxCore_SOURCES "${libmonomuxCore_SOURCES}" PARENT_SCOPE) 5 | -------------------------------------------------------------------------------- /src/server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND libmonomuxCore_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/ClientData.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/Dispatch.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/Server.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/SessionData.cpp 6 | ) 7 | set(libmonomuxCore_SOURCES "${libmonomuxCore_SOURCES}" PARENT_SCOPE) 8 | 9 | list(APPEND libmonomuxImplementation_SOURCES 10 | ${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp 11 | ) 12 | set(libmonomuxImplementation_SOURCES "${libmonomuxImplementation_SOURCES}" PARENT_SCOPE) 13 | -------------------------------------------------------------------------------- /src/server/ClientData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | 21 | #include "monomux/control/PascalString.hpp" 22 | 23 | #include "monomux/server/ClientData.hpp" 24 | 25 | namespace monomux::server 26 | { 27 | 28 | ClientData::ClientData(std::unique_ptr Connection) 29 | : ID(Connection->raw()), Created(std::chrono::system_clock::now()), 30 | ControlConnection(std::move(Connection)), AttachedSession(nullptr) 31 | {} 32 | 33 | static std::size_t NonceCounter = 0; // FIXME: Remove this. 34 | 35 | std::size_t ClientData::consumeNonce() noexcept 36 | { 37 | std::size_t N = Nonce.value_or(0); 38 | Nonce.reset(); 39 | return N; 40 | } 41 | 42 | std::size_t ClientData::makeNewNonce() noexcept 43 | { 44 | // FIXME: Better random number generation. 45 | Nonce.emplace(++NonceCounter); 46 | return *Nonce; 47 | } 48 | 49 | void ClientData::subjugateIntoDataSocket(ClientData& Other) noexcept 50 | { 51 | assert(!DataConnection && "Current client already has a data connection!"); 52 | assert(!Other.DataConnection && 53 | "Other client already has a data connection!"); 54 | DataConnection.swap(Other.ControlConnection); 55 | assert(!Other.ControlConnection && "Other client stayed alive"); 56 | } 57 | 58 | void ClientData::sendDetachReason( 59 | monomux::message::notification::Detached::DetachMode R, 60 | int EC, 61 | std::string Reason) 62 | { 63 | message::sendMessage( 64 | getControlSocket(), 65 | monomux::message::notification::Detached{R, EC, std::move(Reason)}); 66 | } 67 | 68 | } // namespace monomux::server 69 | -------------------------------------------------------------------------------- /src/server/Main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | 21 | #include "monomux/adt/ScopeGuard.hpp" 22 | #include "monomux/server/Server.hpp" 23 | #include "monomux/system/Environment.hpp" 24 | #include "monomux/system/Process.hpp" 25 | #include "monomux/system/Signal.hpp" 26 | #include "monomux/unreachable.hpp" 27 | 28 | #include "ExitCode.hpp" 29 | #include "monomux/server/Main.hpp" 30 | 31 | #include "monomux/Log.hpp" 32 | #define LOG(SEVERITY) monomux::log::SEVERITY("server/Main") 33 | 34 | namespace monomux::server 35 | { 36 | 37 | Options::Options() 38 | : ServerMode(false), Background(true), ExitOnLastSessionTerminate(true) 39 | {} 40 | 41 | std::vector Options::toArgv() const 42 | { 43 | std::vector Ret; 44 | 45 | if (ServerMode) 46 | Ret.emplace_back("--server"); 47 | if (SocketPath.has_value()) 48 | { 49 | Ret.emplace_back("--socket"); 50 | Ret.emplace_back(*SocketPath); 51 | } 52 | if (!Background) 53 | Ret.emplace_back("--no-daemon"); 54 | if (!ExitOnLastSessionTerminate) 55 | Ret.emplace_back("--keepalive"); 56 | 57 | return Ret; 58 | } 59 | 60 | [[noreturn]] void exec(const Options& Opts, const char* ArgV0) 61 | { 62 | MONOMUX_TRACE_LOG(LOG(trace) << "exec() a new server"); 63 | 64 | Process::SpawnOptions SO; 65 | SO.Program = ArgV0; 66 | SO.Arguments = Opts.toArgv(); 67 | 68 | Process::exec(SO); 69 | unreachable("[[noreturn]]"); 70 | } 71 | 72 | namespace 73 | { 74 | 75 | constexpr char ServerObjName[] = "Server"; 76 | 77 | void serverShutdown(SignalHandling::Signal SigNum, 78 | ::siginfo_t* Info, 79 | const SignalHandling* Handling); 80 | void childExited(SignalHandling::Signal SigNum, 81 | ::siginfo_t* Info, 82 | const SignalHandling* Handling); 83 | void coreDumped(SignalHandling::Signal SigNum, 84 | ::siginfo_t* Info, 85 | const SignalHandling* Handling); 86 | 87 | } // namespace 88 | 89 | int main(Options& Opts) 90 | { 91 | std::optional ServerSock; 92 | try 93 | { 94 | ServerSock.emplace(Socket::create(*Opts.SocketPath)); 95 | } 96 | catch (const std::system_error& SE) 97 | { 98 | LOG(fatal) << "Creating the socket '" << *Opts.SocketPath << "' failed:\n\t" 99 | << SE.what(); 100 | if (SE.code() == std::errc::address_in_use) 101 | LOG(info) << "If you are sure another server is not running, delete the " 102 | "file and restart the server."; 103 | return EXIT_SystemError; 104 | } 105 | 106 | Server S = Server(std::move(*ServerSock)); 107 | S.setExitIfNoMoreSessions(Opts.ExitOnLastSessionTerminate); 108 | ScopeGuard Signal{[&S] { 109 | SignalHandling& Sig = SignalHandling::get(); 110 | Sig.registerObject(SignalHandling::ModuleObjName, 111 | "Server"); 112 | Sig.registerObject(ServerObjName, &S); 113 | Sig.registerCallback(SIGHUP, &serverShutdown); 114 | Sig.registerCallback(SIGINT, &serverShutdown); 115 | Sig.registerCallback(SIGTERM, &serverShutdown); 116 | Sig.registerCallback(SIGCHLD, &childExited); 117 | Sig.ignore(SIGPIPE); 118 | Sig.enable(); 119 | 120 | // Override the SIGABRT handler with a custom one that 121 | // kills the server. 122 | Sig.registerCallback(SIGILL, &coreDumped); 123 | Sig.registerCallback(SIGABRT, &coreDumped); 124 | Sig.registerCallback(SIGSEGV, &coreDumped); 125 | Sig.registerCallback(SIGSYS, &coreDumped); 126 | Sig.registerCallback(SIGSTKFLT, &coreDumped); 127 | }, 128 | [] { 129 | SignalHandling& Sig = SignalHandling::get(); 130 | Sig.unignore(SIGPIPE); 131 | Sig.defaultCallback(SIGCHLD); 132 | Sig.defaultCallback(SIGTERM); 133 | Sig.defaultCallback(SIGINT); 134 | Sig.defaultCallback(SIGHUP); 135 | Sig.deleteObject(ServerObjName); 136 | 137 | Sig.clearOneCallback(SIGILL); 138 | Sig.clearOneCallback(SIGABRT); 139 | Sig.clearOneCallback(SIGSEGV); 140 | Sig.clearOneCallback(SIGSYS); 141 | Sig.clearOneCallback(SIGSTKFLT); 142 | }}; 143 | 144 | LOG(info) << "Starting Monomux Server"; 145 | if (Opts.Background) 146 | CheckedPOSIXThrow( 147 | [] { return ::daemon(0, 0); }, "Backgrounding ourselves failed", -1); 148 | 149 | ScopeGuard Server{[&S] { S.loop(); }, [&S] { S.shutdown(); }}; 150 | LOG(info) << "Monomux Server stopped"; 151 | return EXIT_Success; 152 | } 153 | 154 | namespace 155 | { 156 | 157 | /// Handler for request to terinate the server. 158 | void serverShutdown(SignalHandling::Signal /* SigNum */, 159 | ::siginfo_t* /* Info */, 160 | const SignalHandling* Handling) 161 | { 162 | const volatile auto* Srv = 163 | std::any_cast(Handling->getObject(ServerObjName)); 164 | if (!Srv) 165 | return; 166 | (*Srv)->interrupt(); 167 | } 168 | 169 | /// Handler for \p SIGCHLD when a process spawned by the server quits. 170 | void childExited(SignalHandling::Signal /* SigNum */, 171 | ::siginfo_t* Info, 172 | const SignalHandling* Handling) 173 | { 174 | Process::raw_handle CPID = Info->si_pid; 175 | const volatile auto* Srv = 176 | std::any_cast(Handling->getObject(ServerObjName)); 177 | if (!Srv) 178 | return; 179 | (*Srv)->registerDeadChild(CPID); 180 | } 181 | 182 | /// Custom handler for \p SIGABRT. This is even more custom than the handler in 183 | /// the global \p main() as it deals with killing the server first. 184 | void coreDumped(SignalHandling::Signal SigNum, 185 | ::siginfo_t* Info, 186 | const SignalHandling* Handling) 187 | { 188 | serverShutdown(SigNum, Info, Handling); 189 | } 190 | 191 | } // namespace 192 | 193 | } // namespace monomux::server 194 | 195 | #undef LOG 196 | -------------------------------------------------------------------------------- /src/server/SessionData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | 21 | #include "monomux/server/ClientData.hpp" 22 | #include "monomux/system/Pipe.hpp" 23 | #include "monomux/system/Time.hpp" 24 | 25 | #include "monomux/server/SessionData.hpp" 26 | 27 | #include "monomux/Log.hpp" 28 | #define LOG(SEVERITY) monomux::log::SEVERITY("server/SessionData") 29 | 30 | namespace monomux::server 31 | { 32 | 33 | void SessionData::setProcess(Process&& Process) noexcept 34 | { 35 | MainProcess.reset(); 36 | MainProcess.emplace(std::move(Process)); 37 | } 38 | 39 | raw_fd SessionData::getIdentifyingFD() const noexcept 40 | { 41 | if (!hasProcess() || !getProcess().hasPty()) 42 | return fd::Invalid; 43 | 44 | auto& P = const_cast(getProcess()); 45 | return P.getPty()->raw().get(); 46 | } 47 | 48 | ClientData* SessionData::getLatestClient() const 49 | { 50 | MONOMUX_TRACE_LOG(LOG(trace) << "Searching latest active client of \"" << Name 51 | << "\"..."); 52 | ClientData* R = nullptr; 53 | std::optional().lastActive())> Time; 54 | for (ClientData* C : AttachedClients) 55 | { 56 | if (!C->getDataSocket()) 57 | continue; 58 | MONOMUX_TRACE_LOG(LOG(data) 59 | << "\tCandidate client \"" << C->id() 60 | << "\" last active at " << formatTime(C->lastActive())); 61 | auto CTime = C->lastActive(); 62 | if (!Time || *Time < CTime) 63 | { 64 | MONOMUX_TRACE_LOG(LOG(data) << "\t\tSelecting \"" << C->id()); 65 | Time = CTime; 66 | R = C; 67 | } 68 | } 69 | MONOMUX_TRACE_LOG(if (R) LOG(debug) 70 | << "\tSelected client \"" << R->id() << '"'; 71 | else LOG(debug) << "\tNo clients attached";); 72 | return R; 73 | } 74 | 75 | void SessionData::attachClient(ClientData& Client) 76 | { 77 | AttachedClients.emplace_back(&Client); 78 | } 79 | 80 | void SessionData::removeClient(ClientData& Client) noexcept 81 | { 82 | for (auto It = AttachedClients.begin(); It != AttachedClients.end(); ++It) 83 | if (*It == &Client) 84 | { 85 | It = AttachedClients.erase(It); 86 | break; 87 | } 88 | } 89 | 90 | } // namespace monomux::server 91 | 92 | #undef LOG 93 | -------------------------------------------------------------------------------- /src/system/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND libmonomuxCore_SOURCES 2 | ${CMAKE_CURRENT_SOURCE_DIR}/BufferedChannel.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/Channel.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/Environment.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/Event.cpp 6 | ${CMAKE_CURRENT_SOURCE_DIR}/Pipe.cpp 7 | ${CMAKE_CURRENT_SOURCE_DIR}/Process.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/Pty.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/Socket.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/fd.cpp 11 | ) 12 | set(libmonomuxCore_SOURCES "${libmonomuxCore_SOURCES}" PARENT_SCOPE) 13 | 14 | list(APPEND libmonomuxImplementation_SOURCES 15 | ${CMAKE_CURRENT_SOURCE_DIR}/Crash.cpp 16 | ${CMAKE_CURRENT_SOURCE_DIR}/Signal.cpp 17 | ) 18 | set(libmonomuxImplementation_SOURCES "${libmonomuxImplementation_SOURCES}" PARENT_SCOPE) 19 | -------------------------------------------------------------------------------- /src/system/Channel.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "monomux/system/Channel.hpp" 20 | 21 | #include "monomux/Log.hpp" 22 | #define LOG(SEVERITY) monomux::log::SEVERITY("system/Channel") 23 | #define LOG_WITH_IDENTIFIER(SEVERITY) LOG(SEVERITY) << identifier() << ": " 24 | 25 | namespace monomux 26 | { 27 | 28 | Channel::Channel(fd Handle, std::string Identifier, bool NeedsCleanup) 29 | : Handle(std::move(Handle)), Identifier(std::move(Identifier)), 30 | EntityCleanup(NeedsCleanup) 31 | {} 32 | 33 | fd Channel::release() && 34 | { 35 | Identifier = "'); 38 | EntityCleanup = false; 39 | 40 | return std::move(Handle); 41 | } 42 | 43 | std::string Channel::read(std::size_t Bytes) 44 | { 45 | if (failed()) 46 | throw std::system_error{std::make_error_code(std::errc::io_error), 47 | "Channel has failed."}; 48 | 49 | MONOMUX_TRACE_LOG(LOG_WITH_IDENTIFIER(trace) 50 | << "Reading " << Bytes << " bytes..."); 51 | bool Unused; 52 | return readImpl(Bytes, Unused); 53 | } 54 | 55 | std::size_t Channel::write(std::string_view Buffer) 56 | { 57 | if (failed()) 58 | throw std::system_error{std::make_error_code(std::errc::io_error), 59 | "Channel has failed."}; 60 | 61 | MONOMUX_TRACE_LOG(LOG_WITH_IDENTIFIER(trace) 62 | << "Writing " << Buffer.size() << " bytes..."); 63 | bool Unused; 64 | return writeImpl(Buffer, Unused); 65 | } 66 | 67 | } // namespace monomux 68 | 69 | #undef LOG_WITH_IDENTIFIER 70 | #undef LOG 71 | -------------------------------------------------------------------------------- /src/system/Environment.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "monomux/adt/POD.hpp" 28 | #include "monomux/system/CheckedPOSIX.hpp" 29 | 30 | #include "monomux/system/Environment.hpp" 31 | 32 | #include "monomux/Log.hpp" 33 | #define LOG(SEVERITY) monomux::log::SEVERITY("system/Environment") 34 | 35 | namespace monomux 36 | { 37 | 38 | std::string getEnv(const std::string& Key) 39 | { 40 | const char* const Value = std::getenv(Key.c_str()); 41 | if (!Value) 42 | { 43 | MONOMUX_TRACE_LOG(LOG(data) << "getEnv(" << Key << ") -> unset"); 44 | return {}; 45 | } 46 | MONOMUX_TRACE_LOG(LOG(data) << "getEnv(" << Key << ") = " << Value); 47 | return {Value}; 48 | } 49 | 50 | std::pair 51 | makeCurrentMonomuxSessionName(const std::string& SessionName) 52 | { 53 | return {"MONOMUX_SESSION", SessionName}; 54 | } 55 | 56 | std::optional getCurrentMonomuxSessionName() 57 | { 58 | std::string EnvVal = getEnv("MONOMUX_SESSION"); 59 | if (EnvVal.empty()) 60 | return std::nullopt; 61 | return EnvVal; 62 | } 63 | 64 | std::string defaultShell() 65 | { 66 | std::string EnvVar = getEnv("SHELL"); 67 | if (!EnvVar.empty()) 68 | return EnvVar; 69 | 70 | auto Check = [](const std::string& Program) { 71 | LOG(debug) << "Trying Shell program " << Program; 72 | return CheckedPOSIX( 73 | [Prog = Program.c_str()] { 74 | POD StatResult; 75 | return ::stat(Prog, &StatResult); 76 | }, 77 | -1); 78 | }; 79 | 80 | if (Check("/bin/bash")) 81 | return "/bin/bash"; 82 | if (Check("/bin/sh")) 83 | return "/bin/sh"; 84 | 85 | // Did not succeed. 86 | LOG(debug) << "No Shell found."; 87 | return {}; 88 | } 89 | 90 | SocketPath SocketPath::defaultSocketPath() 91 | { 92 | SocketPath R; 93 | 94 | std::string Dir = getEnv("XDG_RUNTIME_DIR"); 95 | if (!Dir.empty()) 96 | { 97 | LOG(debug) << "Socket path under XDG_RUNTIME_DIR"; 98 | return {std::move(Dir), "mnmx", true}; 99 | } 100 | 101 | Dir = getEnv("TMPDIR"); 102 | if (!Dir.empty()) 103 | { 104 | std::string User = getEnv("USER"); 105 | if (!User.empty()) 106 | { 107 | LOG(debug) << "Socket path under TMPDIR for $USER"; 108 | return {std::move(Dir), "mnmx" + std::move(User), false}; 109 | } 110 | LOG(debug) << "Socket path under TMPDIR"; 111 | return {std::move(Dir), "mnmx", false}; 112 | } 113 | 114 | LOG(debug) << "Socket path under hardcoded /tmp"; 115 | return {"/tmp", "mnmx", false}; 116 | } 117 | 118 | SocketPath SocketPath::absolutise(const std::string& Path) 119 | { 120 | LOG(trace) << "Absolutising path \"" << Path << "\"..."; 121 | 122 | POD Result; 123 | if (Path.front() == '/') 124 | { 125 | // If the path begins with a '/', assume it is absolute. 126 | LOG(trace) << '"' << Path << "\" is already absolute."; 127 | std::strncpy(Result, Path.c_str(), PATH_MAX); 128 | } 129 | else 130 | { 131 | auto R = CheckedPOSIX( 132 | [&Path, &Result] { return ::realpath(Path.c_str(), Result); }, nullptr); 133 | if (!R) 134 | { 135 | std::error_code EC = R.getError(); 136 | LOG(trace) << "realpath(" << Path << ") failed: " << EC.message(); 137 | if (EC == std::errc::no_such_file_or_directory /* ENOENT */) 138 | { 139 | // (For files that do not exist, realpath will fail. So we do the 140 | // absolute path conversion ourselves.) 141 | Result.reset(); 142 | R.get() = 143 | CheckedPOSIXThrow([&Result] { return ::realpath(".", Result); }, 144 | "realpath(\".\")", 145 | nullptr); 146 | LOG(trace) << "realpath(.) = " << R.get(); 147 | 148 | std::size_t ReadlinkCurDirPathSize = std::strlen(Result); 149 | if (ReadlinkCurDirPathSize + 1 + Path.size() > PATH_MAX) 150 | throw std::system_error{ 151 | std::make_error_code(std::errc::filename_too_long), "strncat path"}; 152 | 153 | Result[ReadlinkCurDirPathSize] = '/'; 154 | Result[ReadlinkCurDirPathSize + 1] = 0; 155 | std::strncat( 156 | Result + ReadlinkCurDirPathSize, Path.c_str(), Path.size()); 157 | LOG(trace) << "realpath(.) + " << Path << " -> " << Result; 158 | } 159 | else 160 | throw std::system_error{EC, "realpath()"}; 161 | } 162 | assert(R.get() == &Result[0]); 163 | } 164 | 165 | POD Dir; 166 | std::strncpy(Dir, Result, PATH_MAX); 167 | char* DirResult = 168 | CheckedPOSIXThrow([&Dir] { return ::dirname(Dir); }, "dirname()", nullptr); 169 | char* BaseResult = CheckedPOSIXThrow( 170 | [&Result] { return ::basename(Result); }, "basename()", nullptr); 171 | 172 | LOG(trace) << "Path split: dirname = " << DirResult 173 | << "; name = " << BaseResult; 174 | 175 | SocketPath SP; 176 | SP.Path = DirResult; 177 | SP.Filename = BaseResult; 178 | return SP; 179 | } 180 | 181 | std::string SocketPath::toString() const 182 | { 183 | std::ostringstream Buf; 184 | if (!Path.empty()) 185 | Buf << Path << '/'; 186 | Buf << Filename; 187 | return Buf.str(); 188 | } 189 | 190 | std::vector> 191 | MonomuxSession::createEnvVars() const 192 | { 193 | std::vector> R; 194 | R.emplace_back(std::make_pair("MONOMUX_SOCKET", Socket.toString())); 195 | R.emplace_back(std::make_pair("MONOMUX_SESSION", SessionName)); 196 | return R; 197 | } 198 | 199 | std::optional MonomuxSession::loadFromEnv() 200 | { 201 | std::string SocketPath = getEnv("MONOMUX_SOCKET"); 202 | std::string SessionName = getEnv("MONOMUX_SESSION"); 203 | 204 | if (SocketPath.empty() || SessionName.empty()) 205 | return std::nullopt; 206 | 207 | LOG(data) << "Session from environment:\n\tServer socket: " << SocketPath 208 | << "\n\tSession name: " << SessionName; 209 | 210 | MonomuxSession S; 211 | S.Socket.Filename = SocketPath; 212 | S.SessionName = std::move(SessionName); 213 | return S; 214 | } 215 | 216 | } // namespace monomux 217 | 218 | #undef LOG 219 | -------------------------------------------------------------------------------- /src/system/Pty.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "monomux/adt/POD.hpp" 27 | #include "monomux/system/CheckedPOSIX.hpp" 28 | 29 | #include "monomux/system/Pty.hpp" 30 | 31 | #include "monomux/Log.hpp" 32 | #define LOG(SEVERITY) monomux::log::SEVERITY("system/Pty") 33 | #define LOG_WITH_IDENTIFIER(SEVERITY) LOG(SEVERITY) << name() << ": " 34 | 35 | namespace monomux 36 | { 37 | 38 | Pty::Pty() 39 | { 40 | raw_fd MasterFD; 41 | raw_fd SlaveFD; 42 | POD DeviceName; 43 | 44 | CheckedPOSIXThrow( 45 | [&MasterFD, &SlaveFD, &DeviceName] { 46 | return ::openpty(&MasterFD, &SlaveFD, DeviceName, nullptr, nullptr); 47 | }, 48 | "Failed to openpty()", 49 | -1); 50 | 51 | LOG(debug) << "Opened " << DeviceName << " (master: " << MasterFD 52 | << ", slave: " << SlaveFD << ')'; 53 | 54 | Master = MasterFD; 55 | Slave = SlaveFD; 56 | Name = DeviceName; 57 | } 58 | 59 | void Pty::setupParentSide() 60 | { 61 | MONOMUX_TRACE_LOG(LOG_WITH_IDENTIFIER(trace) 62 | << Master << " - set up as parent..."); 63 | 64 | // Close PTS, the slave PTY. 65 | raw_fd PTS = Slave.release(); 66 | fd::close(PTS); 67 | 68 | IsMaster = true; 69 | fd::setNonBlockingCloseOnExec(Master); 70 | 71 | std::ostringstream InName; 72 | std::ostringstream OutName; 73 | InName << "'; 74 | OutName << "'; 75 | 76 | Read = std::make_unique( 77 | Pipe::weakWrap(Master.get(), Pipe::Read, InName.str())); 78 | Write = std::make_unique( 79 | Pipe::weakWrap(Master.get(), Pipe::Write, OutName.str())); 80 | } 81 | 82 | void Pty::setupChildrenSide() 83 | { 84 | MONOMUX_TRACE_LOG(LOG_WITH_IDENTIFIER(trace) 85 | << Slave << " - Set up as child..."); 86 | 87 | // Closes PTM, the pseudoterminal multiplexer master (PTMX). 88 | raw_fd PTM = Master.release(); 89 | fd::close(PTM); 90 | 91 | CheckedPOSIXThrow( 92 | [this] { return ::login_tty(Slave); }, "login_tty in child", -1); 93 | 94 | // Generally the PTY children are exec()ing away, so we can safely just NOT 95 | // set up the Pipe data structures here, right? 96 | } 97 | 98 | void Pty::setSize(unsigned short Rows, unsigned short Columns) 99 | { 100 | if (!isMaster()) 101 | throw std::invalid_argument{"setSize() not allowed on slave device."}; 102 | 103 | MONOMUX_TRACE_LOG(LOG(data) << Master << ": setSize(Rows=" << Rows 104 | << ", Columns=" << Columns << ')'); 105 | 106 | POD Size; 107 | Size->ws_row = Rows; 108 | Size->ws_col = Columns; 109 | CheckedPOSIXThrow( 110 | [RawFD = Master.get(), &Size] { return ::ioctl(RawFD, TIOCSWINSZ, &Size); }, 111 | "ioctl(PTMX, TIOCSWINSZ /* set window size*/);", 112 | -1); 113 | } 114 | 115 | } // namespace monomux 116 | 117 | #undef LOG_WITH_IDENTIFIER 118 | #undef LOG 119 | -------------------------------------------------------------------------------- /src/system/fd.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | #include 21 | 22 | #include "monomux/adt/POD.hpp" 23 | #include "monomux/system/CheckedPOSIX.hpp" 24 | 25 | #include "monomux/system/fd.hpp" 26 | 27 | #include "monomux/Log.hpp" 28 | #define LOG(SEVERITY) monomux::log::SEVERITY("system/fd") 29 | 30 | namespace monomux 31 | { 32 | 33 | std::size_t fd::maxNumFDs() 34 | { 35 | POD Limits; 36 | CheckedPOSIXThrow([&Limits] { return ::getrlimit(RLIMIT_NOFILE, &Limits); }, 37 | "getrlimit()", 38 | -1); 39 | if (Limits->rlim_cur == RLIM_INFINITY) 40 | return -1; 41 | return Limits->rlim_cur; 42 | } 43 | 44 | fd::raw_fd fd::fileno(std::FILE* File) 45 | { 46 | return CheckedPOSIXThrow([File] { return ::fileno(File); }, "fileno()", -1); 47 | } 48 | 49 | fd::fd(raw_fd Handle) noexcept : Handle(Handle) 50 | { 51 | MONOMUX_TRACE_LOG(LOG(data) << "FD #" << Handle << " opened."); 52 | } 53 | 54 | fd fd::dup(raw_fd Handle) 55 | { 56 | raw_fd DupHandle = 57 | CheckedPOSIXThrow([Handle] { return ::dup(Handle); }, "dup()", -1); 58 | return fd{DupHandle}; 59 | } 60 | 61 | void fd::close(raw_fd FD) noexcept 62 | { 63 | MONOMUX_TRACE_LOG(LOG(data) << "Closing FD #" << FD << "..."); 64 | CheckedPOSIX([FD] { return ::close(FD); }, -1); 65 | } 66 | 67 | void fd::addStatusFlag(raw_fd FD, flag_t Flag) noexcept 68 | { 69 | flag_t FlagsNow; 70 | CheckedPOSIX( 71 | [FD, &FlagsNow] { 72 | FlagsNow = ::fcntl(FD, F_GETFL); 73 | return FlagsNow; 74 | }, 75 | -1); 76 | 77 | FlagsNow |= Flag; 78 | 79 | CheckedPOSIX([FD, &FlagsNow] { return ::fcntl(FD, F_SETFL, FlagsNow); }, -1); 80 | } 81 | 82 | void fd::removeStatusFlag(raw_fd FD, flag_t Flag) noexcept 83 | { 84 | flag_t FlagsNow; 85 | CheckedPOSIX( 86 | [FD, &FlagsNow] { 87 | FlagsNow = ::fcntl(FD, F_GETFL); 88 | return FlagsNow; 89 | }, 90 | -1); 91 | 92 | FlagsNow &= (~Flag); 93 | 94 | CheckedPOSIX([FD, &FlagsNow] { return ::fcntl(FD, F_SETFL, FlagsNow); }, -1); 95 | } 96 | 97 | void fd::addDescriptorFlag(raw_fd FD, flag_t Flag) noexcept 98 | { 99 | flag_t FlagsNow; 100 | CheckedPOSIX( 101 | [FD, &FlagsNow] { 102 | FlagsNow = ::fcntl(FD, F_GETFD); 103 | return FlagsNow; 104 | }, 105 | -1); 106 | 107 | FlagsNow |= Flag; 108 | 109 | CheckedPOSIX([FD, &FlagsNow] { return ::fcntl(FD, F_SETFD, FlagsNow); }, -1); 110 | } 111 | 112 | void fd::removeDescriptorFlag(raw_fd FD, flag_t Flag) noexcept 113 | { 114 | flag_t FlagsNow; 115 | CheckedPOSIX( 116 | [FD, &FlagsNow] { 117 | FlagsNow = ::fcntl(FD, F_GETFD); 118 | return FlagsNow; 119 | }, 120 | -1); 121 | 122 | FlagsNow &= (~Flag); 123 | 124 | CheckedPOSIX([FD, &FlagsNow] { return ::fcntl(FD, F_SETFD, FlagsNow); }, -1); 125 | } 126 | 127 | void fd::setNonBlockingCloseOnExec(raw_fd FD) noexcept 128 | { 129 | fd::addStatusFlag(FD, O_NONBLOCK); 130 | fd::addDescriptorFlag(FD, FD_CLOEXEC); 131 | } 132 | 133 | } // namespace monomux 134 | 135 | #undef LOG 136 | -------------------------------------------------------------------------------- /src/unreachable.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | 21 | #include "monomux/unreachable.hpp" 22 | 23 | [[noreturn]] void 24 | unreachable_impl(const char* Msg, const char* File, std::size_t LineNo) 25 | { 26 | std::cerr << "FATAL! UNREACHABLE executed"; 27 | if (File) 28 | std::cerr << " at " << File << ':' << LineNo; 29 | 30 | if (Msg) 31 | std::cerr << ": " << Msg << '\n'; 32 | else 33 | std::cerr << '\n'; 34 | 35 | // [[noreturn]] 36 | std::abort(); 37 | std::_Exit(-4); 38 | } 39 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 2 | set(MONOMUX_BUILD_TESTS_DEFAULT ON) 3 | else() 4 | set(MONOMUX_BUILD_TESTS_DEFAULT OFF) 5 | endif() 6 | 7 | set(MONOMUX_BUILD_TESTS ${MONOMUX_BUILD_TESTS_DEFAULT} CACHE BOOL 8 | "Whether to build the test framework when building the project.") 9 | 10 | if (MONOMUX_BUILD_TESTS) 11 | if (MONOMUX_BUILD_UNITY) 12 | message(WARNING "Unity build is not compatible with testing, but MONOMUX_BUILD_TESTS was supplied. Prioritising unity build and disabling tests...") 13 | set(MONOMUX_BUILD_TESTS OFF) 14 | return() 15 | endif() 16 | 17 | include(FetchContent) 18 | FetchContent_Declare( 19 | googletest 20 | URL http://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip 21 | ) 22 | 23 | set(BUILD_GMOCK OFF) 24 | 25 | # For Windows: Prevent overriding the parent project's compiler/linker 26 | # settings. 27 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 28 | # Do not add GoogleTest stuff as our install() target... 29 | set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # option(INSTALL_GTEST) would override. 30 | set(INSTALL_GTEST OFF) 31 | 32 | # Actually build GoogleTest. 33 | FetchContent_MakeAvailable(googletest) 34 | 35 | include(CTest) 36 | 37 | add_executable(monomux_tests 38 | main.cpp 39 | 40 | adt/RingBufferTest.cpp 41 | adt/SmallIndexMapTest.cpp 42 | control/MessageSerialisationTest.cpp 43 | ) 44 | target_include_directories(monomux_tests PUBLIC 45 | ${CMAKE_SOURCE_DIR}/include 46 | ${CMAKE_SOURCE_DIR}/src 47 | ) 48 | target_link_libraries(monomux_tests PRIVATE 49 | monomuxCore 50 | monomuxImplementation 51 | ) 52 | target_link_libraries(monomux_tests PUBLIC 53 | gtest_main 54 | ) 55 | 56 | add_test(NAME tests 57 | COMMAND monomux_tests 58 | ) 59 | 60 | add_custom_target(check 61 | COMMAND env CTEST_OUTPUT_ON_FAILURE=1 GTEST_COLOR=1 ${CMAKE_CTEST_COMMAND} 62 | DEPENDS monomux_tests) 63 | else() 64 | add_custom_target(check 65 | COMMAND echo "Testing is not supported in this build. Set MONOMUX_BUILD_TESTS=ON or create a Debug build." 66 | COMMAND exit 1 67 | ) 68 | endif() 69 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 Whisperity 3 | * 4 | * SPDX-License-Identifier: GPL-3.0 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "monomux/system/Crash.hpp" 25 | 26 | void stackTrace(int Signal) 27 | { 28 | // Remove this handler and make the OS handle once we return. 29 | (void)std::signal(Signal, SIG_DFL); 30 | 31 | std::cerr << "FATAL! " << Signal << '\n'; 32 | monomux::printBacktrace(std::cerr); 33 | std::cerr << std::endl; 34 | } 35 | 36 | int main(int argc, char *argv[]) 37 | { 38 | ::testing::InitGoogleTest(&argc, argv); 39 | 40 | (void)std::signal(SIGABRT, stackTrace); 41 | (void)std::signal(SIGSEGV, stackTrace); 42 | 43 | return RUN_ALL_TESTS(); 44 | } 45 | --------------------------------------------------------------------------------