├── AUTHORS ├── MAINTAINERS ├── CODEOWNERS ├── tbtxdomain ├── tbtxdomain.rules.in ├── CMakeLists.txt └── tbtxdomain.in ├── tests ├── docker-build.sh ├── Dockerfile └── test-integration-mock.py ├── .travis.yml ├── tbtacl ├── tbtacl.rules.in ├── CMakeLists.txt ├── write.cpp └── tbtacl.in ├── common ├── CMakeLists.txt ├── file.cpp └── file.h ├── tbtadm ├── CMakeLists.txt ├── tbtadm.bash_completion ├── main.cpp ├── controller.h └── controller.cpp ├── Description ├── .gitignore ├── docs ├── CMakeLists.txt └── tbtadm.t2t ├── TESTING.md ├── COPYING ├── .clang-format ├── CMakeLists.txt └── README.md /AUTHORS: -------------------------------------------------------------------------------- 1 | Yehezkel Bernat 2 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Yehezkel Bernat 2 | Michael Jamet 3 | Thunderbolt Software mailing list 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS for autoreview assigning in github 2 | 3 | # These owners will be the default owners for everything in 4 | # the repo. 5 | * @finikorg @ybernat @westeri 6 | -------------------------------------------------------------------------------- /tbtxdomain/tbtxdomain.rules.in: -------------------------------------------------------------------------------- 1 | # Thunderbolt udev rules for XDomain connections 2 | SUBSYSTEM=="thunderbolt" ENV{DEVTYPE}=="thunderbolt_xdomain" ACTION=="add" RUN+="@UDEV_BIN_DIR@/tbtxdomain add $devpath" 3 | -------------------------------------------------------------------------------- /tests/docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -e 5 | # Print executed commands 6 | set -x 7 | 8 | echo "Building in " `pwd` 9 | 10 | rm -rf build && mkdir build 11 | cd build && cmake .. && cmake --build . 12 | 13 | LC_ALL=C.UTF-8 make check 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | services: 4 | - docker 5 | 6 | before_install: 7 | # Build an image from Dockerfile 8 | - docker build -t dockerbuild -f tests/Dockerfile . 9 | 10 | script: 11 | # Run build in Docker 12 | - docker run -e -t -v `pwd`:/usr/local/src/thunderbolt-tools dockerbuild /bin/bash ./tests/docker-build.sh 13 | -------------------------------------------------------------------------------- /tbtacl/tbtacl.rules.in: -------------------------------------------------------------------------------- 1 | # Thunderbolt udev rules for ACL (device auto approval) 2 | SUBSYSTEM=="thunderbolt" ENV{DEVTYPE}=="thunderbolt_device" ACTION=="add" ATTR{authorized}=="0" RUN+="@UDEV_BIN_DIR@/tbtacl add $devpath" 3 | SUBSYSTEM=="thunderbolt" ENV{DEVTYPE}=="thunderbolt_device" ACTION=="change" ATTR{authorized}!="0" RUN+="@UDEV_BIN_DIR@/tbtacl change $devpath" 4 | -------------------------------------------------------------------------------- /tbtxdomain/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TBTXDOMAIN "tbtxdomain") 2 | set(TBTXDOMAIN_RULES "${RULES_PREFIX}-${TBTXDOMAIN}.rules") 3 | 4 | configure_file("${TBTXDOMAIN}.in" ${TBTXDOMAIN} @ONLY) 5 | configure_file("${TBTXDOMAIN}.rules.in" ${TBTXDOMAIN_RULES} @ONLY) 6 | 7 | install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/${TBTXDOMAIN}" 8 | DESTINATION ${UDEV_BIN_DIR}) 9 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TBTXDOMAIN_RULES}" 10 | DESTINATION ${UDEV_RULES_DIR}) 11 | -------------------------------------------------------------------------------- /common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(common VERSION 0.1 LANGUAGES CXX) 2 | 3 | add_library(${PROJECT_NAME} STATIC "file.cpp") 4 | 5 | find_package(Boost REQUIRED COMPONENTS filesystem) 6 | target_link_libraries(${PROJECT_NAME} PUBLIC ${Boost_LIBRARIES}) 7 | 8 | target_include_directories(${PROJECT_NAME} INTERFACE 9 | $) 10 | 11 | target_compile_options(${PROJECT_NAME} PRIVATE 12 | $<$:${TBT_CXXFLAGS}>) 13 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14) 14 | -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:testing 2 | RUN apt-get update -q && apt-get install -y \ 3 | git \ 4 | gobject-introspection \ 5 | libgirepository1.0-dev \ 6 | gir1.2-glib-2.0 \ 7 | gir1.2-gudev-1.0 \ 8 | python3-gi \ 9 | umockdev \ 10 | gir1.2-umockdev-1.0 \ 11 | locales \ 12 | pkg-config \ 13 | udev \ 14 | libudev-dev \ 15 | libgudev-1.0-dev \ 16 | valac \ 17 | autoconf \ 18 | automake \ 19 | libtool \ 20 | libglib2.0-dev \ 21 | cmake \ 22 | libboost-filesystem-dev \ 23 | txt2tags 24 | 25 | WORKDIR /usr/local/src/thunderbolt-tools 26 | -------------------------------------------------------------------------------- /tbtadm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(tbtadm VERSION 0.1 LANGUAGES CXX) 2 | 3 | add_executable(${PROJECT_NAME} "main.cpp" "controller.cpp") 4 | target_link_libraries(${PROJECT_NAME} PRIVATE common) 5 | 6 | target_compile_options(${PROJECT_NAME} PRIVATE 7 | $<$:${TBT_CXXFLAGS}>) 8 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14) 9 | 10 | install(TARGETS ${PROJECT_NAME} 11 | DESTINATION ${CMAKE_INSTALL_BINDIR}) 12 | install(FILES ${PROJECT_NAME}.bash_completion 13 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions 14 | RENAME ${PROJECT_NAME}) 15 | -------------------------------------------------------------------------------- /Description: -------------------------------------------------------------------------------- 1 | User-space components for handling Thunderbolt controller and devices 2 | Thunderbolt™ technology is a transformational high-speed, dual protocol 3 | I/O that provides unmatched performance with up to 40Gbps bi-directional 4 | transfer speeds. It provides flexibility and simplicity by supporting both 5 | data (PCIe, USB3.1) and video (DisplayPort) on a single cable connection 6 | that can daisy-chain up to six devices. 7 | . 8 | This package includes the user-space components for device approval support: 9 | . 10 | 1. Easier interaction with the kernel module for approving connected devices. 11 | . 12 | 2. ACL for auto-approving devices white-listed by the user. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on files from https://github.com/github/gitignore 2 | 3 | *~ 4 | *.autosave 5 | *.orig 6 | *.rej 7 | 8 | # C++ file 9 | 10 | # Prerequisites 11 | *.d 12 | 13 | # Compiled Object files 14 | *.slo 15 | *.lo 16 | *.o 17 | *.obj 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | *_pch.h.cpp 23 | 24 | # Compiled Dynamic libraries 25 | *.so 26 | *.so.* 27 | *.dylib 28 | *.dll 29 | 30 | # Fortran module files 31 | *.mod 32 | *.smod 33 | 34 | # Compiled Static libraries 35 | *.lai 36 | *.la 37 | *.a 38 | *.lib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | 45 | 46 | # CMake file 47 | 48 | CMakeCache.txt 49 | CMakeFiles 50 | CMakeScripts 51 | Testing 52 | Makefile 53 | cmake_install.cmake 54 | install_manifest.txt 55 | compile_commands.json 56 | CTestTestfile.cmake 57 | 58 | 59 | # qtcreator generated files 60 | 61 | *.pro.user* 62 | CMakeLists.txt.user 63 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(FILE_NAME tbtadm) 2 | set(T2T_SOURCE "${FILE_NAME}.t2t") 3 | set(MAN_OUTPUT "${FILE_NAME}.1.gz") 4 | 5 | find_program(TXT2TAGS txt2tags) 6 | if(${TXT2TAGS} STREQUAL "TXT2TAGS-NOTFOUND") 7 | message(FATAL_ERROR "txt2tags is needed for generating the man page") 8 | endif() 9 | 10 | add_custom_command(OUTPUT ${MAN_OUTPUT} 11 | COMMAND txt2tags -t man -i "${CMAKE_CURRENT_SOURCE_DIR}/${T2T_SOURCE}" -o ${MAN_OUTPUT}.tmp 12 | COMMAND gzip -9nc ${MAN_OUTPUT}.tmp > ${MAN_OUTPUT} 13 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 14 | MAIN_DEPENDENCY ${T2T_SOURCE} 15 | COMMENT "Generating man page...") 16 | add_custom_target(manpage ALL DEPENDS ${MAN_OUTPUT}) 17 | 18 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${MAN_OUTPUT}" 19 | DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) 20 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing Thunderbolt user-space components 2 | 3 | ## Overview 4 | To test Thunderbolt `umockdev` package is used. Umockdev mocks Linux devices 5 | to test tools even on platforms without actual Thunderbolt connected. 6 | 7 | ## Testing inside docker container 8 | To ease setting up environment for the testing there is docker configuration. 9 | 10 | ### Testing docker locally 11 | - Install docker following instructions: https://docs.docker.com/get-started/#setup 12 | - Create docker image `make docker-build` 13 | - Run tests `make docker-run` 14 | 15 | ### Testing in Travis CI platform 16 | Check build results and trigger builds can be done here: 17 | https://travis-ci.org/01org/thunderbolt-software-user-space 18 | 19 | ## Testing locally 20 | - Build and install `umockdev` following instructions here: 21 | https://github.com/martinpitt/umockdev 22 | - Use special makefile target: `make check` 23 | -------------------------------------------------------------------------------- /tbtacl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TBTACL "tbtacl") 2 | project(${TBTACL}-write VERSION 0.1 LANGUAGES CXX) 3 | set(TBTACL_RULES "${RULES_PREFIX}-${TBTACL}.rules") 4 | 5 | add_executable(${PROJECT_NAME} "write.cpp") 6 | target_link_libraries(${PROJECT_NAME} PRIVATE common) 7 | 8 | target_compile_options(${PROJECT_NAME} PRIVATE 9 | $<$:${TBT_CXXFLAGS}>) 10 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14) 11 | 12 | configure_file("${TBTACL}.in" ${TBTACL} @ONLY) 13 | configure_file("${TBTACL}.rules.in" ${TBTACL_RULES} @ONLY) 14 | 15 | install(TARGETS ${PROJECT_NAME} 16 | RUNTIME DESTINATION ${UDEV_BIN_DIR}) 17 | install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/${TBTACL}" 18 | DESTINATION ${UDEV_BIN_DIR}) 19 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TBTACL_RULES}" 20 | DESTINATION ${UDEV_RULES_DIR}) 21 | -------------------------------------------------------------------------------- /tbtadm/tbtadm.bash_completion: -------------------------------------------------------------------------------- 1 | _tbtadm() 2 | { 3 | local cur prev opts acl devices 4 | 5 | acl=/var/lib/thunderbolt/acl 6 | devices=/sys/bus/thunderbolt/devices 7 | 8 | COMPREPLY=() 9 | cur="$2" 10 | command="${COMP_WORDS[1]}" 11 | opts="devices peers topology approve approve-all acl add remove remove-all" 12 | 13 | case "$command" in 14 | approve|add|remove) 15 | local routestrings 16 | routestrings="$( [ -d ${devices} ] && command ls ${devices} | command grep -v domain | command grep -Fv . | command grep -v [0-9]-0)" 17 | COMPREPLY+=( $(compgen -W "${routestrings}" -- "$cur") ) 18 | ;;& 19 | approve|approve-all) 20 | COMPREPLY+=( $(compgen -W "--once" -- "$cur") ) 21 | ;; 22 | remove) 23 | local uuids 24 | uuids="$( [ -d ${acl} ] && command ls ${acl})" 25 | COMPREPLY+=( $(compgen -W "${uuids}" -- "$cur") ) 26 | ;; 27 | *) 28 | if [[ ${COMP_CWORD} = 1 ]]; then 29 | COMPREPLY=( $(compgen -W "${opts}" -- "$cur") ) 30 | fi 31 | ;; 32 | esac; 33 | 34 | return 0 35 | } 36 | 37 | complete -F _tbtadm tbtadm 38 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Thunderbolt(TM) user-space components 2 | These components are distributed under the following BSD-style license: 3 | 4 | Copyright(c) 2017 Intel Corporation 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of Intel Corporation nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /tbtxdomain/tbtxdomain.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ################################################################################ 4 | # Thunderbolt(TM) tbtxdomain tool 5 | # This code is distributed under the following BSD-style license: 6 | # 7 | # Copyright(c) 2017 Intel Corporation. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # * Redistributions of source code must retain the above copyright notice, 13 | # this list of conditions and the following disclaimer. 14 | # * Redistributions in binary form must reproduce the above copyright 15 | # notice, this list of conditions and the following disclaimer in the 16 | # documentation and/or other materials provided with the distribution. 17 | # * Neither the name of Intel Corporation nor the names of its contributors 18 | # may be used to endorse or promote products derived from this software 19 | # without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 25 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | ################################################################################ 33 | 34 | PATH=$PATH:/sbin:/usr/sbin 35 | 36 | log="logger -t tbxdomain $$:" 37 | $log args: $* 38 | 39 | # Currently just load the Thunderbolt networking driver whenever XDomain 40 | # connection appears. In future we may want to allow user to select 41 | # preferred Thunderbolt service or disable the functionality completely. 42 | if modprobe --first-time -q thunderbolt-net; then 43 | $log Thunderbolt networking service loaded 44 | fi 45 | -------------------------------------------------------------------------------- /docs/tbtadm.t2t: -------------------------------------------------------------------------------- 1 | tbtadm 2 | Yehezkel Bernat 3 | %%mtime(%m/%d/%Y) 4 | 5 | %!encoding: UTF-8 6 | 7 | 8 | = NAME = 9 | tbtadm - Thunderbolt(tm) management tool 10 | 11 | 12 | = SYNOPSIS = 13 | 14 | **tbtadm** 15 | 16 | **tbtadm devices** 17 | 18 | **tbtadm peers** 19 | 20 | **tbtadm topology** 21 | 22 | **tbtadm approve** [--once] 23 | 24 | **tbtadm approve-all** [--once] 25 | 26 | **tbtadm acl** 27 | 28 | **tbtadm add** 29 | 30 | **tbtadm remove** 31 | 32 | **tbtadm remove-all** 33 | 34 | 35 | = DESCRIPTION = 36 | **tbtadm** provides convenient way to interact with **Thunderbolt** kernel 37 | module, approve the connection of Thunderbolt devices, handle the ACL for 38 | auto-connecting devices and more. 39 | Running it with no argument, it prints all the available commands. 40 | 41 | 42 | = OPTIONS = 43 | 44 | : **devices** 45 | Print a list of all the currently connected Thunderbolt devices in the following 46 | format: 47 | ``` 48 | Route-string Vendor Device name Authorized? In ACL? 49 | ``` 50 | 51 | : **peers** 52 | Print a list of all the currently connected hosts in the following 53 | format: 54 | ``` 55 | Route-string Vendor Device name 56 | ``` 57 | 58 | : **topology** 59 | Print all the currently connected Thunderbolt devices in a tree, starting with 60 | the controller itself, resembling the device connection topology. 61 | 62 | : **approve** [--once] 63 | If the selected Thunderbolt device isn't authorized, approve it and (if ``--once`` 64 | wasn't specified) add it to ACL. 65 | 66 | : **approve-all** [--once] 67 | Approve all currently connected Thunderbolt devices that aren't authorized yet 68 | and (if ``--once`` wasn't specified) add them to ACL. 69 | 70 | : **acl** 71 | Print the ACL content in the following format: 72 | ``` 73 | UUID Vendor Device name Currently connected? 74 | ``` 75 | 76 | : **add** 77 | Add a device to ACL. The argument selects the device to be added by its 78 | route-string. Doesn't work in SL2 (secure; key-based) as addition to ACL must be 79 | done together with the device authorization (with ``approve`` or ``approve-all`` 80 | without ``--once`` flag). 81 | 82 | : **remove** 83 | Remove ACL entry. The argument selects the device to be removed by its UUID or 84 | (if it's currently connected) by route-string. 85 | 86 | : **remove-all** 87 | Clear the ACL, removing all the entries. 88 | -------------------------------------------------------------------------------- /tbtadm/main.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Thunderbolt(TM) tbtadm tool 3 | * This code is distributed under the following BSD-style license: 4 | * 5 | * Copyright(c) 2017 Intel Corporation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Intel Corporation nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | ******************************************************************************/ 31 | 32 | #include 33 | #include "controller.h" 34 | 35 | int main(int argc, char* argv[]) try 36 | { 37 | tbtadm::Controller(argc, argv, std::cout, std::cerr).run(); 38 | } 39 | catch (std::system_error& e) 40 | { 41 | std::cerr << e.code() << ' ' << e.what() << '\n'; 42 | return e.code().value(); 43 | } 44 | catch (std::exception& e) 45 | { 46 | std::cerr << "Exception: " << e.what() << '\n'; 47 | return EXIT_FAILURE; 48 | } 49 | catch (...) 50 | { 51 | std::cerr << "Unknown exception\n"; 52 | return EXIT_FAILURE; 53 | } 54 | -------------------------------------------------------------------------------- /tbtacl/write.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Thunderbolt(TM) tbtacl tool 3 | * This code is distributed under the following BSD-style license: 4 | * 5 | * Copyright(c) 2017 Intel Corporation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Intel Corporation nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | *******************************************************************************/ 31 | 32 | #include "file.h" 33 | 34 | /* 35 | * The reason for this file, instead of writing the file directly from tbtacl 36 | * script is because shell returns a generic error for all errors, but tbtacl 37 | * cares about the exact errno returned from driver (see there). 38 | * 39 | * This file is intended to be used from tbtacl script only so no (direct) input 40 | * validation is done, besides what already done inside File class 41 | * implementation. 42 | */ 43 | 44 | int main(int /*argc*/, char* argv[]) try 45 | { 46 | tbtadm::File file(argv[2], tbtadm::File::Mode::Write); 47 | file << std::string(argv[1]); 48 | } 49 | catch (std::system_error& e) 50 | { 51 | return e.code().value(); 52 | } 53 | catch (...) 54 | { 55 | return EXIT_FAILURE; 56 | } 57 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | ConstructorInitializerIndentWidth: 4 6 | ContinuationIndentWidth: 4 7 | IndentWidth: 4 8 | TabWidth: 4 9 | AlignAfterOpenBracket: Align 10 | AlignConsecutiveAssignments: true 11 | AlignConsecutiveDeclarations: false # maybe will be turned on when it works better 12 | AlignEscapedNewlinesLeft: true 13 | AlignOperands: true 14 | AlignTrailingComments: true 15 | AllowAllParametersOfDeclarationOnNextLine: false 16 | AllowShortBlocksOnASingleLine: false 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: Inline 19 | AllowShortIfStatementsOnASingleLine: false 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: true 24 | BinPackArguments: false 25 | BinPackParameters: false 26 | BraceWrapping: 27 | AfterClass: true 28 | AfterControlStatement: true 29 | AfterEnum: true 30 | AfterFunction: true 31 | AfterNamespace: true 32 | AfterObjCDeclaration: true 33 | AfterStruct: true 34 | AfterUnion: true 35 | BeforeCatch: true 36 | BeforeElse: true 37 | IndentBraces: true 38 | BreakBeforeBinaryOperators: NonAssignment 39 | BreakBeforeBraces: Allman 40 | BreakBeforeTernaryOperators: true 41 | BreakConstructorInitializersBeforeComma: false 42 | ColumnLimit: 80 43 | CommentPragmas: '^ IWYU pragma:' 44 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 45 | Cpp11BracedListStyle: true 46 | DerivePointerAlignment: false 47 | DisableFormat: false 48 | ExperimentalAutoDetectBinPacking: false 49 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 50 | # Include sorting is off because it looks buggy 51 | #IncludeCategories: 52 | # - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 53 | # Priority: 2 54 | # - Regex: '^(<|"(gtest|isl|json)/)' 55 | # Priority: 3 56 | # - Regex: '.*' 57 | # Priority: 1 58 | IndentCaseLabels: false 59 | IndentWrappedFunctionNames: false 60 | KeepEmptyLinesAtTheStartOfBlocks: true 61 | MacroBlockBegin: '' 62 | MacroBlockEnd: '' 63 | MaxEmptyLinesToKeep: 1 64 | NamespaceIndentation: None 65 | ObjCBlockIndentWidth: 4 66 | ObjCSpaceAfterProperty: false 67 | ObjCSpaceBeforeProtocolList: true 68 | PenaltyBreakBeforeFirstCallParameter: 19 69 | PenaltyBreakComment: 300 70 | PenaltyBreakFirstLessLess: 0 71 | PenaltyBreakString: 1000 72 | PenaltyExcessCharacter: 1000000 73 | PenaltyReturnTypeOnItsOwnLine: 60 74 | SortIncludes: false # Off because it looks buggy 75 | PointerAlignment: Left 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | UseTab: Never 88 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(thunderbolt-user-space) 4 | 5 | set(VERSION "0.9.3") 6 | 7 | include(GNUInstallDirs) 8 | 9 | include(FindPkgConfig) 10 | pkg_get_variable(PKG_CONFIG_UDEV_DIR udev udevdir) 11 | 12 | set(UDEV_RULES_DIR "${PKG_CONFIG_UDEV_DIR}/rules.d" CACHE PATH "Install path for udev rules") 13 | set(UDEV_BIN_DIR "${PKG_CONFIG_UDEV_DIR}" CACHE PATH "Install path for udev-triggered executables") 14 | set(RULES_PREFIX "60" CACHE PATH "The numeric prefix for udev rules file") 15 | 16 | set(TBT_CXXFLAGS ${CXX_FLAGS} -Wall -Wextra) 17 | 18 | add_subdirectory(common) 19 | add_subdirectory(tbtacl) 20 | add_subdirectory(tbtxdomain) 21 | add_subdirectory(tbtadm) 22 | add_subdirectory(docs) 23 | 24 | configure_file(tests/test-integration-mock.py tests/test-integration-mock.py COPYONLY) 25 | configure_file(tests/Dockerfile tests/Dockerfile COPYONLY) 26 | 27 | add_custom_target(check 28 | COMMAND umockdev-wrapper python3 tests/test-integration-mock.py 29 | DEPENDS tests/test-integration-mock.py tbtadm 30 | ) 31 | 32 | set(DOCKER_IMAGE "thunderbolt-tools") 33 | 34 | set(DOCKER_BUILD_CMD 35 | docker build 36 | --tag ${DOCKER_IMAGE} 37 | --file tests/Dockerfile 38 | . 39 | ) 40 | add_custom_target(docker-build 41 | COMMAND ${DOCKER_BUILD_CMD} 42 | DEPENDS tests/Dockerfile 43 | ) 44 | 45 | set(DOCKER_RUN_CMD 46 | docker run 47 | --env 48 | --tty 49 | --volume ${CMAKE_CURRENT_LIST_DIR}:/usr/local/src/thunderbolt-tools 50 | ${DOCKER_IMAGE} 51 | /bin/bash tests/docker-build.sh 52 | ) 53 | add_custom_target(docker-run 54 | COMMAND ${DOCKER_RUN_CMD} 55 | DEPENDS tests tbtadm 56 | ) 57 | 58 | set(PACKAGE_SUMMARY "User-space components for handling Thunderbolt controller and devices") 59 | 60 | set(CPACK_RPM_COMPONENT_INSTALL ON) 61 | set(CPACK_DEB_COMPONENT_INSTALL ON) 62 | set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) 63 | set(CPACK_STRIP_FILES ON) 64 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/Description") 65 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") 66 | set(CPACK_GENERATOR "DEB;RPM") 67 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PACKAGE_SUMMARY}) 68 | set(CPACK_PACKAGE_RELEASE 1) 69 | set(CPACK_PACKAGE_CONTACT "Thunderbolt Software mailing list ") 70 | set(CPACK_PACKAGE_VENDOR "Intel") 71 | set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) 72 | 73 | # RPM specific 74 | set(CPACK_PACKAGE_RELOCATABLE OFF) 75 | set(CPACK_RPM_PACKAGE_REQUIRES "boost-filesystem") 76 | 77 | function(ALL_ANCESTOR_DIRS result dir) 78 | while(NOT dir STREQUAL "/") 79 | list(APPEND LOCAL_LIST "${dir}") 80 | get_filename_component(dir "${dir}/.." ABSOLUTE) 81 | endwhile() 82 | set(${result} "${LOCAL_LIST}" PARENT_SCOPE) 83 | endfunction() 84 | 85 | foreach(dir "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}" 86 | "${UDEV_RULES_DIR}" 87 | "${UDEV_BIN_DIR}" 88 | "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1" 89 | "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions") 90 | ALL_ANCESTOR_DIRS(LIST_FOR_RPM "${dir}") 91 | list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${LIST_FOR_RPM}") 92 | list(REMOVE_DUPLICATES CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION) 93 | endforeach() 94 | 95 | # DEB specific 96 | file(READ "${CPACK_PACKAGE_DESCRIPTION_FILE}" PACKAGE_DESCRIPTION) 97 | set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${PACKAGE_DESCRIPTION}) 98 | 99 | # Main customization points 100 | set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) 101 | set(CPACK_PACKAGE_VERSION ${VERSION}) 102 | set(CPACK_PACKAGE_FILE_NAME 103 | "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CMAKE_SYSTEM_PROCESSOR}") 104 | 105 | # For DEB 106 | install(FILES ${CPACK_RESOURCE_FILE_LICENSE} 107 | DESTINATION share/doc/${CPACK_PACKAGE_NAME} 108 | RENAME copyright) 109 | 110 | include(CPack) 111 | -------------------------------------------------------------------------------- /tbtadm/controller.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Thunderbolt(TM) tbtadm tool 3 | * This code is distributed under the following BSD-style license: 4 | * 5 | * Copyright(c) 2017 Intel Corporation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Intel Corporation nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | ******************************************************************************/ 31 | 32 | #pragma once 33 | 34 | #include 35 | #include 36 | 37 | #include 38 | 39 | namespace fs = boost::filesystem; 40 | 41 | namespace tbtadm 42 | { 43 | 44 | class Controller 45 | { 46 | public: 47 | static constexpr int UnkownSL = -1; 48 | 49 | Controller(int argc, char* argv[], std::ostream& out, std::ostream& err); 50 | void run(); 51 | 52 | private: 53 | /// Prints all connected devices 54 | void devices(); 55 | 56 | /// Prints all connected peers (hosts) 57 | void peers(); 58 | 59 | /// Prints all connected devices in a tree 60 | void topology(); 61 | 62 | /// Add to tree all devices under a given path 63 | struct ControllerInTree; 64 | void createTree(ControllerInTree& controller, const fs::path& path); 65 | 66 | void printTree(std::string& indentation, 67 | const std::map& map); 68 | 69 | void printDetails(bool last, 70 | std::string& indentation, 71 | const std::vector& details); 72 | 73 | /// Goes over all domains and approves all the connected devices 74 | void approveAll(); 75 | 76 | /// Approves the given device and its descendants 77 | void approveAll(const fs::path& dir); 78 | 79 | /// Approves the given device 80 | void approve(const fs::path& dir); 81 | 82 | /// Adds to ACL the given device 83 | void addToACL(const fs::path& dir); 84 | 85 | /// Prints ACL 86 | void acl(); 87 | 88 | /// Add the given device to ACL 89 | void add(const fs::path& dir); 90 | 91 | /// Removes the given UUID from ACL 92 | void remove(std::string uuid); 93 | 94 | /// Clears the ACL 95 | void removeAll(); 96 | 97 | int m_argc; 98 | char** m_argv; 99 | std::ostream& m_out; 100 | std::ostream& m_err; 101 | int m_sl = UnkownSL; // FIXME: Consider moving to a local var 102 | bool m_once = false; 103 | }; 104 | 105 | } // namespace tbtadm 106 | -------------------------------------------------------------------------------- /common/file.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Thunderbolt(TM) tbtadm tool 3 | * This code is distributed under the following BSD-style license: 4 | * 5 | * Copyright(c) 2017 Intel Corporation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Intel Corporation nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | ******************************************************************************/ 31 | 32 | #include "file.h" 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | namespace fs = boost::filesystem; 39 | 40 | namespace 41 | { 42 | [[noreturn]] void throwErrno() 43 | { 44 | throw std::system_error(errno, std::system_category()); 45 | } 46 | } // namespace 47 | 48 | tbtadm::File::File(const fs::path& path, Mode mode, int flags, int perm) 49 | : File(path.string(), mode, flags, perm) 50 | { 51 | } 52 | 53 | tbtadm::File::File(const std::string& filename, Mode mode, int flags, int perm) 54 | : File(filename.c_str(), mode, flags, perm) 55 | { 56 | } 57 | 58 | tbtadm::File::File(const char* filename, Mode mode, int flags, int perm) 59 | : m_fd(perm ? ::open(filename, static_cast(mode) | flags, perm) 60 | : ::open(filename, static_cast(mode) | flags)) 61 | { 62 | if (m_fd == ERROR) 63 | { 64 | throwErrno(); 65 | } 66 | } 67 | 68 | tbtadm::File::~File() 69 | { 70 | close(); 71 | } 72 | 73 | tbtadm::File::File(tbtadm::File&& other) noexcept 74 | { 75 | std::swap(m_fd, other.m_fd); 76 | } 77 | 78 | tbtadm::File& tbtadm::File::operator=(tbtadm::File&& other) noexcept 79 | { 80 | close(); 81 | std::swap(m_fd, other.m_fd); 82 | return *this; 83 | } 84 | 85 | void tbtadm::File::write(const std::string& value) 86 | { 87 | errno = 0; 88 | auto ret = ::write(m_fd, value.data(), value.size()); 89 | if (ret == ERROR || (!ret && errno)) 90 | { 91 | throwErrno(); 92 | } 93 | } 94 | 95 | std::string tbtadm::File::read() 96 | { 97 | std::string content; 98 | errno = 0; 99 | while (true) 100 | { 101 | char c; 102 | auto ret = ::read(m_fd, &c, sizeof(c)); 103 | if (ret == ERROR || (!ret && errno)) 104 | { 105 | throwErrno(); 106 | } 107 | if (!ret) 108 | { 109 | break; 110 | } 111 | content.push_back(c); 112 | } 113 | if (content.empty()) 114 | { 115 | throw std::runtime_error("No data could be read"); 116 | } 117 | return content; 118 | } 119 | 120 | void tbtadm::File::close() 121 | { 122 | if (m_fd != ERROR) 123 | { 124 | ::close(m_fd); 125 | m_fd = ERROR; 126 | } 127 | } 128 | 129 | void tbtadm::chdir(const fs::path& dir) 130 | { 131 | if (::chdir(dir.c_str()) == File::ERROR) 132 | { 133 | throwErrno(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tbtacl/tbtacl.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ################################################################################ 4 | # Thunderbolt(TM) tbtacl tool 5 | # This code is distributed under the following BSD-style license: 6 | # 7 | # Copyright(c) 2017 Intel Corporation. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # * Redistributions of source code must retain the above copyright notice, 13 | # this list of conditions and the following disclaimer. 14 | # * Redistributions in binary form must reproduce the above copyright 15 | # notice, this list of conditions and the following disclaimer in the 16 | # documentation and/or other materials provided with the distribution. 17 | # * Neither the name of Intel Corporation nor the names of its contributors 18 | # may be used to endorse or promote products derived from this software 19 | # without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 25 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | ################################################################################ 33 | 34 | log="logger -t tbtacl $$:" 35 | $log args: "$*" 36 | 37 | acltree=/var/lib/thunderbolt/acl 38 | write_helper=@UDEV_BIN_DIR@/tbtacl-write 39 | 40 | action=$1 41 | device=/sys$2 42 | 43 | debug() { 44 | $log "$*" 45 | } 46 | 47 | die() { 48 | debug "$*" 49 | exit 1 50 | } 51 | 52 | # The main function to authorize a specific device. The parameter it expects is 53 | # the full sysfs path to the device. Then, if the device is found in ACL, it 54 | # authorizes it. 55 | authorize() { 56 | 57 | # TOCTOU protection: chdir so if an attacker replaces the device between 58 | # the read of unique_id and the write of authorized, the write will fail 59 | cd "$1" || { debug "can't access $1" ; return 1 ; } 60 | 61 | $log authorizing "$1" 62 | 63 | uuid=$( cat unique_id ) 64 | [ -n "$uuid" ] || { debug -p err no UUID; return 1 ; } # Exit if UUID read failed 65 | 66 | [ -e "$acltree/$uuid" ] || { debug not in ACL ; return 1 ; } # Exit if UUID isn't in ACL 67 | 68 | if [ "$sl" -eq 2 ]; then 69 | # Exit if device doesn't support SL2 or key is empty 70 | [ -e key ] || { debug "device doesn't support SL2"; return 1 ; } 71 | [ -e "$acltree/$uuid/key" ] || { debug no key found ; return 1 ; } 72 | 73 | cat "$acltree/$uuid/key" > key 74 | $log key found 75 | fi 76 | 77 | $write_helper "$sl" authorized 78 | err=$? 79 | if which errno; then 80 | errstr=$( errno $err | cut -d' ' -f1 ) 81 | fi 82 | 83 | $log "authorization result: $err $errstr" 84 | 85 | case "$err" in 86 | 126|129) # ENOKEY or EKEYREJECTED 87 | rm -f "$acltree/$uuid/key" 88 | debug invalid key removed, reapprove 89 | udevadm trigger -c change "$1" # Not needed if GUI watchs $acltree/$uuid/key 90 | ;; 91 | esac 92 | } 93 | 94 | 95 | # Find the domain and extract the current SL 96 | domain=$device 97 | while [ -n "$domain" ] && [ "$domain" != '/' ]; do 98 | basename "$domain" | grep -Fq "domain" && break 99 | domain=$( dirname "$domain" ) 100 | done 101 | 102 | sl=$( cat "$domain/security" ) 103 | case "$sl" in 104 | user) sl=1 105 | ;; 106 | 107 | secure) sl=2 108 | ;; 109 | 110 | *) die SL is $sl, leaving... 111 | ;; 112 | esac 113 | 114 | 115 | case "$action" in 116 | # New device attached, go to authorize it 117 | add) authorize "$device" 118 | ;; 119 | 120 | # The device got authorized, let's try to authorize again the devices 121 | # behind it 122 | change) list="$device/*/authorized" # this glob is expanded later 123 | # Stop if no substitution was done (no relevant childs found) 124 | echo $list | grep -Fvq '*' || die no childs found 125 | 126 | for i in $list ; do 127 | i=$( dirname "$i" ) 128 | [ -e "$i/uevent" ] || continue 129 | if grep -Fxq 'DEVTYPE=thunderbolt_device' "$i/uevent"; then 130 | authorize "$i" 131 | fi 132 | done 133 | ;; 134 | 135 | *) die "unhandled action: $action" 136 | ;; 137 | esac 138 | -------------------------------------------------------------------------------- /common/file.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Thunderbolt(TM) tbtadm tool 3 | * This code is distributed under the following BSD-style license: 4 | * 5 | * Copyright(c) 2017 Intel Corporation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Intel Corporation nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | ******************************************************************************/ 31 | 32 | #pragma once 33 | 34 | #include 35 | 36 | #include // for O_RDONLY, O_WRONLY 37 | 38 | #include 39 | 40 | namespace tbtadm 41 | { 42 | /** 43 | * @brief This class wraps-around POSIX file interface for C++ style usage 44 | * 45 | * The class encapsulates the actions to be type-safe, translates errors to 46 | * exceptions so they will not get ignored and implements RAII-style handling of 47 | * the file. 48 | * 49 | * The operations work on the file as whole, e.g. reading the whole file 50 | * together, as this is the only use-case we need right now. 51 | * 52 | * The assumption here is that the class is used only for sysfs files (e.g. non- 53 | * seekable). 54 | * 55 | * The reason all this is done instead of simply using the already existing C++ 56 | * streams is because we need the real errno value in case of an error, while 57 | * streams return a generic error even while setting the stream to throw on 58 | * error and can easily overwrite errno by other actions done after the failing 59 | * operations. 60 | * C functions could be used instead of POSIX but write error doesn't appear 61 | * until flushing the file, so POSIX is a better choice for this use-case. 62 | */ 63 | class File 64 | { 65 | public: 66 | enum class Mode 67 | { 68 | Read = O_RDONLY, 69 | Write = O_WRONLY 70 | }; 71 | 72 | /** 73 | * @brief Open the file 74 | * 75 | * @param filename Name/path of the file to open 76 | * @param mode File open mode 77 | */ 78 | File(const boost::filesystem::path& path, 79 | Mode mode, 80 | int flags = 0, 81 | int perm = 0); 82 | 83 | /** 84 | * @brief Open the file 85 | * 86 | * @param filename Name/path of the file to open 87 | * @param mode File open mode 88 | */ 89 | File(const std::string& filename, Mode mode, int flags = 0, int perm = 0); 90 | 91 | /** 92 | * @brief Open the file 93 | * 94 | * @param filename Name/path of the file to open 95 | * @param mode File open mode 96 | * 97 | * This overloading exists so call with a string literal wouldn't be 98 | * ambiguous 99 | */ 100 | File(const char* filename, Mode mode, int flags = 0, int perm = 0); 101 | 102 | /** 103 | * @brief close the file 104 | */ 105 | ~File(); 106 | 107 | File(const File&) = delete; 108 | File& operator=(const File&) = delete; 109 | 110 | File(File&& other) noexcept; 111 | File& operator=(File&& other) noexcept; 112 | 113 | /** 114 | * @brief write the string content 115 | * 116 | * @param value content to write 117 | */ 118 | void write(const std::string& value); 119 | 120 | /** 121 | * @brief read the whole file 122 | * 123 | * @return A string with the file content 124 | */ 125 | std::string read(); 126 | 127 | static const int ERROR = -1; 128 | 129 | private: 130 | void close(); 131 | 132 | int m_fd = ERROR; 133 | }; 134 | 135 | void chdir(const boost::filesystem::path& dir); 136 | 137 | inline File& operator<<(File& file, const std::string& t) 138 | { 139 | file.write(t); 140 | return file; 141 | } 142 | 143 | template 144 | File& operator<<(File& file, const T& t) 145 | { 146 | file.write(std::to_string(t)); 147 | return file; 148 | } 149 | } // namespace tbtadm 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DISCONTINUATION OF PROJECT. 2 | 3 | This project will no longer be maintained by Intel. 4 | 5 | Intel has ceased development and contributions including, but not limited to, maintenance, bug fixes, new releases, or updates, to this project. 6 | 7 | Intel no longer accepts patches to this project. 8 | 9 | If you have an ongoing need to use this project, are interested in independently developing it, or would like to maintain patches for the open source software community, please create your own fork of this project. 10 | # Thunderbolt(TM) user-space components 11 | 12 | [![Build Status](https://travis-ci.org/intel/thunderbolt-software-user-space.svg?branch=master)](https://travis-ci.org/intel/thunderbolt-software-user-space) 13 | 14 | ## License 15 | These components are distributed under a BSD-style license. See COPYING for the 16 | full license. 17 | 18 | 19 | ## Overview 20 | Thunderbolt™ technology is a transformational high-speed, dual protocol 21 | I/O that provides unmatched performance with up to 40Gbps bi-directional 22 | transfer speeds. It provides flexibility and simplicity by supporting both 23 | data (PCIe, USB3.1) and video (DisplayPort) on a single cable connection 24 | that can daisy-chain up to six devices. 25 | 26 | 27 | ## Features 28 | The user-space components implement device approval support: 29 | 1. Easier interaction with the kernel module for approving connected devices. 30 | 2. ACL for auto-approving devices white-listed by the user. 31 | 32 | 33 | ## tbtacl 34 | tbtacl is intended to be triggered by udev (see the udev rules in tbtacl.rules). 35 | It auto-approves devices that are found in ACL. 36 | 37 | 38 | ## tbtadm 39 | tbtadm is a user-facing CLI tool. It provides operations for device approval, 40 | handling the ACL and more. 41 | 42 | 43 | ## Supported OSes 44 | - Ubuntu* 16.04 and 17.04 45 | - Fedora* 26 46 | - Clear Linux* 47 | 48 | 49 | ## Kernel/Daemon Compatibility 50 | The user-space components operate in coordination with the upstream Thunderbolt 51 | kernel driver (found in v4.13) to provide the Thunderbolt functionalities. These 52 | components are NOT compatible with the old out-of-tree Thunderbolt kernel 53 | module. 54 | 55 | 56 | ## Build instructions 57 | ### Build dependencies 58 | Build dependencies are: 59 | - CMake 60 | - boost.filesystem 61 | - txt2tags (for generating the man page) 62 | 63 | You also need a c++ compiler with C++14 support and gzip. 64 | 65 | Tested with: 66 | - g++ - v5.4 and v7.1.1 67 | - CMake - v3.5.1 and v3.9.1 68 | - boost - v1.58 and v1.63 69 | - txt2tags - v2.5 and v2.6 70 | 71 | For example, on Ubuntu you can install the dependencies with the following 72 | command: 73 | `sudo apt-get install cmake libboost-filesystem-dev txt2tags pkg-config` 74 | 75 | On Fedora, use this: 76 | `dnf install cmake boost-devel txt2tags` 77 | 78 | ### Building 79 | Use the CMakeLists.txt file found in the root directory to build the project. 80 | For example (run it in the directory holding the code): 81 | 1. `mkdir build` 82 | 2. `cd build` 83 | 3. `cmake .. -DCMAKE_BUILD_TYPE=Release` 84 | 4. `cmake --build .` 85 | 86 | On step 3, `CMAKE_INSTALL_PREFIX`, `UDEV_BIN_DIR` and `UDEV_RULES_DIR` variables 87 | can be used for changing the default installation location, e.g. to install 88 | `tbtadm` under `/usr/bin` instead of the default `/usr/local/bin` run: 89 | `cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr` 90 | 91 | ### Installation 92 | Installation can be done in one of 2 options: 93 | - From build directory, run `cmake --build . --target install`. 94 | - From build directory, run `cpack -G RPM` to create an RPM package or 95 | `cpack -G DEB` to create a DEB package. Then, use your distro package manager 96 | to install the resulted package. 97 | 98 | 99 | ## Changelog 100 | ### v0.9.3 101 | - xdomain: added loading Thunderbolt networking driver automatically on XDomain 102 | connection 103 | - tbtadm: added `peers` command and XDomain is now shown in the topology output 104 | - tbtadm: added `add` command for adding to ACL database without `approve` 105 | command 106 | - tbtadm: fixed adding to ACL database in security level 0 107 | - tbtadm: fixed Coverity error reformatting string array initialization 108 | - tbtadm: fixed multi-controller topology tree 109 | - tbtadm: improved readability of console output 110 | - tests: added automatic testing in umockdev simulated environment with docker 111 | - build: remove unneeded `libboost-program-options` dependency 112 | 113 | ### v0.9.2 114 | - tbtadm: added `--once` flag for `approve-all` command 115 | - tbtadm: `approve` command added 116 | - tbtadm: bash completion support added (GitHub issue #27) 117 | - tbtacl: udev dir config variable default values are taken from `pkg-config udev` 118 | - tbtadm: handle empty vendor/device name correctly (GitHub issue #25) 119 | 120 | ### v0.9.1 121 | - Build definition updated to support configuration, installation and packaging 122 | - Documentation update (GitHub issue #23) 123 | - man page added (GitHub issue #9) 124 | - Fixes for documentation (GitHub issue #20) 125 | - Build definition updated (GitHub issues #21, #22) 126 | - tbtadm: Compilation warnings (GitHub issue #22) 127 | 128 | ### v0.9 129 | - First official release 130 | - tbtacl: use C++ instead of Python for write action (GitHub issue #19) 131 | 132 | ### Eng. drop 2 133 | - tbtadm: more commands added (devices, topology, acl) 134 | - tbtadm: 'remove' accepts route-string, not only UUID 135 | - tbtadm: 'remove-all' prints removed entry count 136 | - tbtadm: future compatibility with xdomain changes 137 | - tbtacl: use sh instead of bash 138 | - tbtacl: improved error reporting (using write.py to get the actual errno) 139 | - tbtadm, tbtacl, tbtacl.rules: improvement and bug fixes in SL2 support 140 | - tbtacl: fixed SL2 handling 141 | - tbtacl: don't assume errno(1) is installed 142 | - tbtacl.rules: correctly handle change with authorized==2 (for SL2) 143 | - tbtadm: correctly handle multi-controller systems 144 | - tbtadm: 'approve-all' - do nothing if SL isn't 1 or 2 145 | - tbtadm: 'approve-all' - add key on SL2 146 | - tbtadm: removing non-existing ACL entry is just a warning, not an error 147 | - tbtadm: File class reports errors more accurately for write() and read() 148 | 149 | 150 | ## Known Issues 151 | - tbtadm should use a helper + polkit for better permission handling 152 | - error reporting can be improved 153 | - bash completion rules are less strict about completions than what `tbtadm` 154 | actually accepts 155 | 156 | 157 | ## Information 158 | The source for this code: 159 | - https://github.com/01org/thunderbolt-software-user-space 160 | 161 | Mailing list: 162 | - thunderbolt-software@lists.01.org 163 | - Register at: https://lists.01.org/mailman/listinfo/thunderbolt-software 164 | - Archives at: https://lists.01.org/pipermail/thunderbolt-software/ 165 | 166 | For additional information about Thunderbolt technology visit: 167 | - https://01.org/thunderbolt-sw 168 | - https://thunderbolttechnology.net/ 169 | -------------------------------------------------------------------------------- /tests/test-integration-mock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # thunderbolt-tools intergation tests. Modified version of 4 | # bolt integration test suite 5 | # 6 | # Copyright © 2017 Red Hat, Inc 7 | # Copyright © 2017 Intel Corp 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # Authors: 20 | # Christian J. Kellner 21 | # Andrei Emeltchenko 22 | 23 | import binascii 24 | import os 25 | import shutil 26 | import sys 27 | import subprocess 28 | import unittest 29 | import uuid 30 | import tempfile 31 | 32 | import re 33 | 34 | import logging 35 | log = logging.getLogger(__name__) 36 | 37 | import shlex 38 | 39 | from itertools import chain 40 | 41 | try: 42 | import gi 43 | from gi.repository import GLib 44 | from gi.repository import Gio 45 | gi.require_version('UMockdev', '1.0') 46 | from gi.repository import UMockdev 47 | 48 | except ImportError as e: 49 | sys.stderr.write('Skipping integration test due to missing depdendencies: %s\n' % str(e)) 50 | sys.exit(0) 51 | 52 | # Configuration 53 | TBTADM = "tbtadm/tbtadm" 54 | ACL = "/var/lib/thunderbolt/acl" 55 | VENDOR = "Mock Vendor" 56 | DEVICE_NAME = "Thunderbolt Cable" 57 | 58 | # Mock Device Tree 59 | class Device(object): 60 | subsystem = "unknown" 61 | udev_attrs = [] 62 | udev_props = [] 63 | 64 | def __init__(self, name, children): 65 | self._parent = None 66 | self.children = [self._adopt(c) for c in children] 67 | self.udev = None 68 | self.name = name 69 | self.syspath = None 70 | 71 | def _adopt(self, device): 72 | device.parent = self 73 | return device 74 | 75 | def _get_own(self, items): 76 | i = chain.from_iterable([a, str(getattr(self, a.lower()))] for a in items) 77 | return list(i) 78 | 79 | def collect(self, predicate): 80 | children = self.children 81 | head = [self] if predicate(self) else [] 82 | tail = chain.from_iterable(c.collect(predicate) for c in children) 83 | return head + list(filter(predicate, tail)) 84 | 85 | def first(self, predicate): 86 | if predicate(self): 87 | return self 88 | for c in self.children: 89 | found = c.first(predicate) 90 | if found: 91 | return found 92 | 93 | @property 94 | def parent(self): 95 | return self._parent 96 | 97 | @parent.setter 98 | def parent(self, value): 99 | self._parent = value 100 | 101 | @property 102 | def root(self): 103 | return self if self.parent is None else self.parent.root 104 | 105 | def connect_tree(self, bed): 106 | self.connect(bed) 107 | for c in self.children: 108 | c.connect_tree(bed) 109 | 110 | def connect(self, bed): 111 | assert self.syspath is None 112 | attributes = self._get_own(self.udev_attrs) 113 | properties = self._get_own(self.udev_props) 114 | sysparent = self.parent and self.parent.syspath 115 | self.syspath = bed.add_device(self.subsystem, 116 | self.name, 117 | sysparent, 118 | attributes, 119 | properties) 120 | self.testbed = bed 121 | print('Connected ' + self.name + ' ' + self.syspath) 122 | 123 | def disconnect(self, bed): 124 | for c in self.children: 125 | c.disconnect(bed) 126 | print('disconnecting ' + self.name + ' ' + self.syspath) 127 | bed.remove_device(self.syspath) 128 | self.authorized = 0 129 | self.key = "" 130 | self.syspath = None 131 | 132 | # Thunderbolt device class 133 | class TbDevice(Device): 134 | subsystem = "thunderbolt" 135 | devtype = "thunderbolt_device" 136 | 137 | udev_attrs = ['authorized', 138 | 'device', 139 | 'device_name', 140 | 'key', 141 | 'unique_id', 142 | 'vendor', 143 | 'vendor_name'] 144 | 145 | udev_props = ['DEVTYPE'] 146 | 147 | def __init__(self, name, device_name=None, authorized=0, vendor=None, 148 | uid=None, children=None): 149 | super(TbDevice, self).__init__(name, children or []) 150 | self.unique_id = uid or str(uuid.uuid4()) 151 | self.device_name = device_name or 'Thunderbolt ' + name 152 | self.device = self._make_id(self.device_name) 153 | self.vendor_name = vendor or 'Mock Device' 154 | self.vendor = self._make_id(self.vendor_name) 155 | self.authorized = authorized 156 | self.key = "" 157 | 158 | def _make_id(self, name): 159 | return '0x%X' % binascii.crc32(name.encode('utf-8')) 160 | 161 | @property 162 | def authorized_file(self): 163 | if self.syspath is None: 164 | return None 165 | return os.path.join(self.syspath, 'authorized') 166 | 167 | @property 168 | def domain(self): 169 | return self.parent.domain 170 | 171 | @staticmethod 172 | def is_unauthorized(d): 173 | return isinstance(d, TbDevice) and d.authorized == 0 174 | 175 | class TbHost(TbDevice): 176 | def __init__(self, children, index = 0): 177 | super(TbHost, self).__init__('%d-0' % index, 178 | authorized=1, 179 | uid='3b7d4bad-4fdf-44ff-8730-ffffdeadbabe', 180 | device_name='Controller', 181 | children=children) 182 | 183 | def connect(self, bed): 184 | self.authorized = 1 185 | super(TbHost, self).connect(bed) 186 | 187 | class TbDomain(Device): 188 | subsystem = "thunderbolt" 189 | devtype = "thunderbolt_domain" 190 | 191 | udev_attrs = ['security'] 192 | udev_props = ['DEVTYPE'] 193 | 194 | SECURITY_NONE = 'none' 195 | SECURITY_USER = 'user' 196 | SECURITY_SECURE = 'secure' 197 | 198 | def __init__(self, security = SECURITY_SECURE, index = 0, host = None): 199 | assert host 200 | assert isinstance(host, TbHost) 201 | name = 'domain%d' % index 202 | super(TbDomain, self).__init__(name, children=[host]) 203 | self.security = security 204 | 205 | @property 206 | def devices(self): 207 | return self.collect(lambda c: isinstance(c, TbDevice)) 208 | 209 | @property 210 | def domain(self): 211 | return self 212 | 213 | # Test Suite 214 | class thunderbolt_test(unittest.TestCase): 215 | @classmethod 216 | def setUp(self): 217 | self.testbed = UMockdev.Testbed.new() 218 | print("\nPreparing test case\n") 219 | # Remove ACL database before each test case 220 | for root, dirs, files in os.walk(ACL, topdown=False): 221 | for name in files: 222 | os.remove(os.path.join(root, name)) 223 | for name in dirs: 224 | os.rmdir(os.path.join(root, name)) 225 | 226 | def tearDown(self): 227 | print(self) 228 | log.debug("Tear down test case") 229 | 230 | # mock tree stuff 231 | def default_mock_tree(self): 232 | # default mock tree 233 | tree = TbDomain(host=TbHost([ 234 | TbDevice('0-1', device_name = DEVICE_NAME, vendor = VENDOR)])) 235 | return tree 236 | 237 | # Authorized security level 0 device tree 238 | def authorized_mock_tree(self, index = 0): 239 | # Tree with security level 0 240 | host = TbHost([TbDevice('%d-1' % index, device_name = DEVICE_NAME, 241 | vendor = VENDOR, authorized = 1)], index = index) 242 | tree = TbDomain(security = TbDomain.SECURITY_NONE, index = index, 243 | host = host) 244 | 245 | return tree 246 | 247 | # Parse tbtadm devices 248 | def get_device_line(self, route): 249 | u = subprocess.check_output( 250 | shlex.split("%s devices" % TBTADM)).decode("utf-8") 251 | lines = u.splitlines() 252 | for l in lines: 253 | if re.findall("^%s" % route, l): 254 | return l 255 | 256 | # Parse tbtadm topology 257 | def extract_property(self, u, prop): 258 | lines = u.splitlines() 259 | for l in lines: 260 | seclevel = re.findall(".*%s: (.*)" % prop, l) 261 | if seclevel: 262 | log.debug("%s: %s", prop, seclevel[0]) 263 | return seclevel[0] 264 | 265 | def get_info(self): 266 | u = subprocess.check_output( 267 | shlex.split("%s topology" % TBTADM)).decode("utf-8") 268 | log.debug(u) 269 | return u 270 | 271 | def get_seclevel(self): 272 | return self.extract_property(self.get_info(), "Security level") 273 | 274 | def get_authorized(self): 275 | return self.extract_property(self.get_info(), "Authorized") 276 | 277 | def get_acl_status(self): 278 | return self.extract_property(self.get_info(), "In ACL") 279 | 280 | def get_uuid(self): 281 | return self.extract_property(self.get_info(), "UUID") 282 | 283 | # the actual tests 284 | def test_tbtadm_devices(self): 285 | # connect all device 286 | tree = self.default_mock_tree() 287 | tree.connect_tree(self.testbed) 288 | 289 | output = self.get_device_line("0-1") 290 | log.debug(output) 291 | self.assertTrue(VENDOR in output) 292 | self.assertTrue(DEVICE_NAME in output) 293 | self.assertTrue("non-authorized" in output) 294 | self.assertTrue("not in ACL" in output) 295 | 296 | # disconnect all devices 297 | tree.disconnect(self.testbed) 298 | 299 | # Get security level through tbtadm topology 300 | def test_tbtadm_domain_seclevel(self): 301 | # connect all device 302 | tree = self.default_mock_tree() 303 | tree.connect_tree(self.testbed) 304 | 305 | # Test default security secure (SL2) 306 | seclevel = self.get_seclevel() 307 | self.assertEqual(seclevel, "SL2 (secure)") 308 | 309 | # Set security to None (SL0) 310 | tree.testbed.set_attribute("/sys/bus/thunderbolt/devices/domain0", 311 | "security", tree.SECURITY_NONE) 312 | 313 | seclevel = self.get_seclevel() 314 | self.assertEqual(seclevel, "SL0 (none)") 315 | 316 | # Set security to User (SL1) 317 | tree.testbed.set_attribute("/sys/bus/thunderbolt/devices/domain0", 318 | "security", tree.SECURITY_USER) 319 | 320 | seclevel = self.get_seclevel() 321 | self.assertEqual(seclevel, "SL1 (user)") 322 | 323 | # disconnect all devices 324 | tree.disconnect(self.testbed) 325 | 326 | def test_tbtadm_authorization_sl0(self): 327 | # connect all device 328 | tree = self.authorized_mock_tree() 329 | tree.connect_tree(self.testbed) 330 | 331 | # Check security is User (SL0) 332 | seclevel = self.get_seclevel() 333 | self.assertEqual(seclevel, "SL0 (none)") 334 | 335 | # Check authorized 336 | authorized = self.get_authorized() 337 | self.assertEqual(authorized, "Yes") 338 | 339 | # Check ACL presence 340 | in_acl = self.get_acl_status() 341 | self.assertEqual(in_acl, "No") 342 | 343 | # Get uuid 344 | uuid = self.get_uuid() 345 | self.assertNotEqual(uuid, None) 346 | 347 | output = subprocess.check_output(shlex.split("%s approve-all" % TBTADM)) 348 | self.assertTrue(b'Approval not relevant in SL0' in output) 349 | 350 | output = subprocess.check_output(shlex.split("%s approve --once 0-1" % TBTADM)) 351 | self.assertTrue(b'Already authorized' in output) 352 | 353 | output = subprocess.check_output(shlex.split("%s approve 0-1" % TBTADM)) 354 | self.assertTrue(b'Already authorized' in output) 355 | 356 | output = subprocess.check_output(shlex.split("%s add 0-1" % TBTADM)) 357 | self.assertTrue(b'Adding to ACL is not relevant in SL0' in output) 358 | 359 | # ACL should not exist 360 | self.assertFalse(os.path.isdir(ACL + "/" + uuid)) 361 | 362 | output = subprocess.check_output(shlex.split("%s acl" % TBTADM)) 363 | log.debug(output) 364 | self.assertTrue(b'ACL is empty' in output) 365 | 366 | output = subprocess.check_output(shlex.split("%s devices" % TBTADM)) 367 | log.debug(output) 368 | 369 | output = self.get_device_line("0-1") 370 | self.assertTrue(VENDOR in output) 371 | self.assertTrue(DEVICE_NAME in output) 372 | self.assertFalse("non-authorized" in output) 373 | self.assertTrue("not in ACL" in output) 374 | 375 | # Test remove 376 | output = subprocess.check_output(shlex.split("%s remove 0-1" % TBTADM)) 377 | self.assertTrue(b'ACL entry doesn\'t exist' in output) 378 | 379 | output = subprocess.check_output(shlex.split("%s remove-all" % TBTADM)) 380 | self.assertTrue(b'ACL is empty' in output) 381 | 382 | # disconnect all devices 383 | tree.disconnect(self.testbed) 384 | 385 | # Test authorization in SL1 (approve --once) 386 | def test_tbtadm_authorization_sl1(self): 387 | # connect all device 388 | tree = self.default_mock_tree() 389 | tree.connect_tree(self.testbed) 390 | 391 | # Set security to User (SL1) 392 | tree.testbed.set_attribute(tree.syspath, "security", tree.SECURITY_USER) 393 | seclevel = self.get_seclevel() 394 | self.assertEqual(seclevel, "SL1 (user)") 395 | 396 | authorized = self.get_authorized() 397 | self.assertEqual(authorized, "No") 398 | 399 | uuid = self.get_uuid() 400 | self.assertNotEqual(uuid, None) 401 | 402 | output = subprocess.check_output(shlex.split("%s approve --once 0-1" % TBTADM)) 403 | self.assertTrue(b'Authorized' in output) 404 | 405 | authorized = self.get_authorized() 406 | self.assertEqual(authorized, "Yes") 407 | 408 | # ACL should not exist 409 | self.assertFalse(os.path.isdir(ACL + "/" + uuid)) 410 | 411 | # Test that second authorization returns "Already authorized" 412 | output = subprocess.check_output(shlex.split("%s approve --once 0-1" % TBTADM)) 413 | self.assertTrue(b'Already authorized' in output) 414 | 415 | # disconnect all devices 416 | tree.disconnect(self.testbed) 417 | 418 | # Test authorization and ACL management in SL1 mode 419 | def test_tbtadm_approve_sl1(self): 420 | # connect all device 421 | tree = self.default_mock_tree() 422 | tree.connect_tree(self.testbed) 423 | 424 | # Set security to User (SL1) 425 | tree.testbed.set_attribute(tree.syspath, "security", tree.SECURITY_USER) 426 | seclevel = self.get_seclevel() 427 | self.assertEqual(seclevel, "SL1 (user)") 428 | 429 | authorized = self.get_authorized() 430 | self.assertEqual(authorized, "No") 431 | 432 | in_acl = self.get_acl_status() 433 | self.assertEqual(in_acl, "No") 434 | 435 | uuid = self.get_uuid() 436 | self.assertNotEqual(uuid, None) 437 | 438 | # ACL should not yet exist 439 | self.assertFalse(os.path.isdir(ACL + "/" + uuid)) 440 | 441 | output = subprocess.check_output(shlex.split("%s approve 0-1" % TBTADM)) 442 | self.assertTrue(b'Authorized' in output) 443 | self.assertTrue(b'Added to ACL' in output) 444 | 445 | authorized = self.get_authorized() 446 | self.assertEqual(authorized, "Yes") 447 | 448 | in_acl = self.get_acl_status() 449 | self.assertEqual(in_acl, "Yes") 450 | 451 | # ACL entry should be created for given UUID 452 | self.assertTrue(os.path.isdir(ACL + "/" + uuid)) 453 | 454 | # Verify content of ACL directory 455 | ls = os.listdir(ACL + "/" + uuid) 456 | ls.sort() 457 | self.assertTrue(ls == ['device_name', 'vendor_name']) 458 | 459 | # disconnect all devices 460 | tree.disconnect(self.testbed) 461 | 462 | # Test authorization in SL2 (approve --once) 463 | def test_tbtadm_authorization_sl2(self): 464 | # connect all device 465 | tree = self.default_mock_tree() 466 | tree.connect_tree(self.testbed) 467 | 468 | # Check security level 469 | seclevel = self.get_seclevel() 470 | self.assertEqual(seclevel, "SL2 (secure)") 471 | 472 | authorized = self.get_authorized() 473 | self.assertEqual(authorized, "No") 474 | 475 | uuid = self.get_uuid() 476 | self.assertNotEqual(uuid, None) 477 | 478 | output = subprocess.check_output(shlex.split("%s approve --once 0-1" % TBTADM)) 479 | self.assertTrue(b'Authorized' in output) 480 | 481 | authorized = self.get_authorized() 482 | self.assertEqual(authorized, "Yes") 483 | 484 | # ACL should not exist 485 | self.assertFalse(os.path.isdir(ACL + "/" + uuid)) 486 | 487 | # Test that second authorization returns "Already authorized" 488 | output = subprocess.check_output(shlex.split("%s approve --once 0-1" % TBTADM)) 489 | log.debug(output) 490 | self.assertTrue(b'Already authorized' in output) 491 | 492 | # disconnect all devices 493 | tree.disconnect(self.testbed) 494 | 495 | # Test authorization and ACL management in SL2 mode 496 | def test_tbtadm_approve_sl2(self): 497 | # connect all device 498 | tree = self.default_mock_tree() 499 | tree.connect_tree(self.testbed) 500 | 501 | # Check security level 502 | seclevel = self.get_seclevel() 503 | self.assertEqual(seclevel, "SL2 (secure)") 504 | 505 | authorized = self.get_authorized() 506 | self.assertEqual(authorized, "No") 507 | 508 | in_acl = self.get_acl_status() 509 | self.assertEqual(in_acl, "No") 510 | 511 | uuid = self.get_uuid() 512 | self.assertNotEqual(uuid, None) 513 | 514 | # ACL should not yet exist 515 | self.assertFalse(os.path.isdir(ACL + "/" + uuid)) 516 | 517 | output = subprocess.check_output(shlex.split("%s approve 0-1" % TBTADM)) 518 | self.assertTrue(b'Authorized' in output) 519 | self.assertTrue(b'Added to ACL' in output) 520 | self.assertTrue(b'Key saved in ACL' in output) 521 | 522 | authorized = self.get_authorized() 523 | self.assertEqual(authorized, "Yes") 524 | 525 | in_acl = self.get_acl_status() 526 | self.assertEqual(in_acl, "Yes") 527 | 528 | # Check also "acl" command 529 | output = subprocess.check_output(shlex.split("%s acl" % TBTADM)) 530 | self.assertTrue(str.encode(uuid) in output) 531 | 532 | # ACL entry should be created for given UUID 533 | self.assertTrue(os.path.isdir(ACL + "/" + uuid)) 534 | 535 | # Verify content of ACL directory 536 | ls = os.listdir(ACL + "/" + uuid) 537 | ls.sort() 538 | self.assertTrue(ls == ['device_name', 'key','vendor_name']) 539 | 540 | # disconnect all devices 541 | tree.disconnect(self.testbed) 542 | 543 | # Test multi - controller device tree 544 | def test_x(self): 545 | # connect all device 546 | device1 = TbDevice("Device1") 547 | device2 = TbDevice("Device2", children = [device1]) 548 | tree1 = TbDomain(host = TbHost([device2])) 549 | tree1.connect_tree(self.testbed) 550 | 551 | device3 = TbDevice("Device3") 552 | device4 = TbDevice("Device4", children = [device3]) 553 | tree2 = TbDomain(host = TbHost([device4], index = 1), index = 1) 554 | tree2.connect_tree(self.testbed) 555 | 556 | subprocess.run(shlex.split("%s topology" % TBTADM)) 557 | 558 | # disconnect all devices 559 | tree1.disconnect(self.testbed) 560 | tree2.disconnect(self.testbed) 561 | 562 | if __name__ == '__main__': 563 | # run ourselves under umockdev 564 | if 'umockdev' not in os.environ.get('LD_PRELOAD', ''): 565 | os.execvp('umockdev-wrapper', ['umockdev-wrapper'] + sys.argv) 566 | 567 | loglevel = logging.DEBUG 568 | logging.basicConfig(level=loglevel) 569 | 570 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) 571 | -------------------------------------------------------------------------------- /tbtadm/controller.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Thunderbolt(TM) tbtadm tool 3 | * This code is distributed under the following BSD-style license: 4 | * 5 | * Copyright(c) 2017 Intel Corporation. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Intel Corporation nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | ******************************************************************************/ 31 | 32 | #include "controller.h" 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "file.h" 42 | 43 | using namespace std::string_literals; 44 | 45 | namespace 46 | { 47 | const fs::path acltree = "/var/lib/thunderbolt/acl"; 48 | const fs::path sysfsDevicesPath = "/sys/bus/thunderbolt/devices"; 49 | 50 | const std::string uniqueIDFilename = "unique_id"; 51 | const std::string authorizedFilename = "authorized"; 52 | const std::string vendorFilename = "vendor_name"; 53 | const std::string deviceFilename = "device_name"; 54 | const std::string keyFilename = "key"; 55 | const std::string securityFilename = "security"; 56 | 57 | const std::string domain = "domain"; 58 | const std::string hostRouteString = "-0"; 59 | const std::string domainDevtype = "DEVTYPE=thunderbolt_domain"; 60 | const std::string deviceDevtype = "DEVTYPE=thunderbolt_device"; 61 | const std::string xdomainDevtype = "DEVTYPE=thunderbolt_xdomain"; 62 | 63 | const std::string opt_devices = "devices"; 64 | const std::string opt_peers = "peers"; 65 | const std::string opt_topology = "topology"; 66 | const std::string opt_approve = "approve"; 67 | const std::string opt_approve_all = "approve-all"; 68 | const std::string opt_acl = "acl"; 69 | const std::string opt_add = "add"; 70 | const std::string opt_remove = "remove"; 71 | const std::string opt_remove_all = "remove-all"; 72 | const std::string opt_once_flag = "--once"; 73 | 74 | const std::string indent = "│ "; 75 | const size_t indentLength = 4; 76 | const std::string indentLast = " "; 77 | 78 | enum security_level 79 | { 80 | SECURITY_LEVEL_NONE = 0, 81 | SECURITY_LEVEL_USER, 82 | SECURITY_LEVEL_SECURE, 83 | SECURITY_LEVEL_DPONLY, 84 | }; 85 | 86 | const std::string SYMBOL_PIPE = "│"; 87 | const std::string SYMBOL_L = "└─ "; 88 | const std::string SYMBOL_PLUS = "├─ "; 89 | 90 | const std::string green = "\x1b[0;32m"; 91 | const std::string yellow = "\x1b[0;33m"; 92 | const std::string normal = "\x1b[0m"; 93 | 94 | class Highlight 95 | { 96 | public: 97 | Highlight(std::ostream& out, const std::string& color) 98 | : m_out(out), m_useColor(::isatty(STDOUT_FILENO)) 99 | { 100 | if (m_useColor) 101 | m_out << color; 102 | } 103 | 104 | ~Highlight() 105 | { 106 | if (m_useColor) 107 | m_out << normal; 108 | } 109 | 110 | private: 111 | std::ostream& m_out; 112 | bool m_useColor; 113 | }; 114 | 115 | std::string read(const fs::path& path) 116 | { 117 | tbtadm::File file(path, tbtadm::File::Mode::Read); 118 | auto content = file.read(); 119 | return content; 120 | } 121 | 122 | /* Trim right characters */ 123 | std::string rtrim(const std::string& str, const std::string& chars = " \n\r") 124 | { 125 | return str.substr(0, str.find_last_not_of(chars) + 1); 126 | } 127 | 128 | std::string readAndTrim(const fs::path& path) 129 | { 130 | return rtrim(read(path)); 131 | } 132 | 133 | /** 134 | * Return the content of the file from given path or "Unknown" + type if empty 135 | * 136 | * @param path path to file to read from 137 | * @param type string to return if file is empty, prefixed with "Unknown" 138 | */ 139 | std::string readName(const fs::path& path, const std::string& type) 140 | { 141 | try 142 | { 143 | auto res = readAndTrim(path); 144 | if (!res.empty()) 145 | { 146 | return res; 147 | } 148 | } 149 | catch (std::runtime_error&) 150 | { 151 | // assuming this is from an empty file 152 | } 153 | return "Unknown " + type; 154 | } 155 | 156 | std::string readVendor(const fs::path& path) 157 | { 158 | return readName(path, "vendor"); 159 | } 160 | 161 | std::string readDevice(const fs::path& path) 162 | { 163 | return readName(path, "device"); 164 | } 165 | 166 | bool findUeventAttr(const fs::path& path, const std::string& attribute) 167 | { 168 | const auto ueventFile = path / "uevent"; 169 | if (!fs::exists(ueventFile)) 170 | { 171 | return false; 172 | } 173 | try 174 | { 175 | const auto uevent = read(ueventFile); 176 | return uevent.find(attribute) != uevent.npos; 177 | } 178 | // assuming this is from an empty uevent file 179 | catch (std::runtime_error&) 180 | { 181 | return false; 182 | } 183 | } 184 | 185 | bool isDomain(const fs::path& path) 186 | { 187 | return findUeventAttr(path, domainDevtype); 188 | } 189 | 190 | bool isRouteString(const std::string& str) 191 | { 192 | return str.size() > 1 && str[1] == '-' && str.find('.') == str.npos; 193 | } 194 | 195 | bool isHost(const std::string& str) 196 | { 197 | return str.size() == 3 && str.substr(1) == hostRouteString; 198 | } 199 | 200 | bool isDevice(const fs::path& path) 201 | { 202 | return findUeventAttr(path, deviceDevtype) 203 | && !isHost(path.filename().string()); 204 | } 205 | 206 | bool isXDomain(const fs::path& path) 207 | { 208 | return findUeventAttr(path, xdomainDevtype); 209 | } 210 | 211 | struct SLDetails 212 | { 213 | int num; 214 | std::string desc; 215 | }; 216 | 217 | const std::map slMap{{"none", {0, "SL0 (none)"}}, 218 | {"user", {1, "SL1 (user)"}}, 219 | {"secure", {2, "SL2 (secure)"}}, 220 | {"dponly", {3, "SL3 (dponly)"}}}; 221 | 222 | int findSL() 223 | { 224 | if (fs::exists(sysfsDevicesPath)) 225 | { 226 | for (auto& dir : fs::directory_iterator(sysfsDevicesPath)) 227 | { 228 | if (!is_directory(dir)) 229 | { 230 | continue; 231 | } 232 | if (isDomain(dir.path())) 233 | { 234 | return slMap.find(readAndTrim(dir.path() / securityFilename)) 235 | ->second.num; 236 | } 237 | } 238 | } 239 | 240 | return tbtadm::Controller::UnkownSL; 241 | } 242 | 243 | bool sysfsDeviceExists() 244 | { 245 | if (!fs::exists(sysfsDevicesPath)) 246 | { 247 | std::cerr << "no thunderbolt devices found\n"; 248 | return false; 249 | } 250 | 251 | return true; 252 | } 253 | } // namespace 254 | 255 | tbtadm::Controller::Controller(int argc, 256 | char* argv[], 257 | std::ostream& out, 258 | std::ostream& err) 259 | : m_argc(argc), m_argv(argv), m_out(out), m_err(err) 260 | { 261 | } 262 | 263 | void tbtadm::Controller::run() 264 | { 265 | if (m_argc >= 2) 266 | { 267 | if (m_argv[1] == opt_devices) 268 | { 269 | return devices(); 270 | } 271 | if (m_argv[1] == opt_peers) 272 | { 273 | return peers(); 274 | } 275 | if (m_argv[1] == opt_topology) 276 | { 277 | return topology(); 278 | } 279 | if (m_argv[1] == opt_approve) 280 | { 281 | if (m_argc == 3 || m_argc == 4) 282 | { 283 | if (m_argv[2] == opt_once_flag) 284 | { 285 | m_once = true; 286 | } 287 | m_sl = findSL(); 288 | return approve(sysfsDevicesPath / m_argv[m_argc - 1]); 289 | } 290 | } 291 | if (m_argv[1] == opt_approve_all) 292 | { 293 | if (m_argc == 3 && m_argv[2] == opt_once_flag) 294 | { 295 | m_once = true; 296 | } 297 | return approveAll(); 298 | } 299 | if (m_argv[1] == opt_acl) 300 | { 301 | return acl(); 302 | } 303 | if (m_argv[1] == opt_add) 304 | { 305 | if (m_argc == 3) 306 | { 307 | m_sl = findSL(); 308 | return add(sysfsDevicesPath / m_argv[2]); 309 | } 310 | } 311 | if (m_argv[1] == opt_remove) 312 | { 313 | if (m_argc == 3) 314 | { 315 | return remove(m_argv[2]); 316 | } 317 | } 318 | if (m_argv[1] == opt_remove_all) 319 | { 320 | return removeAll(); 321 | } 322 | } 323 | 324 | // TODO: help 325 | const std::string sep = " | "; 326 | m_out << "Usage: " << opt_devices << sep << opt_peers << sep << opt_topology 327 | << sep << opt_approve << " [" << opt_once_flag << "] " 328 | << sep << opt_approve_all << " [" << opt_once_flag << ']' << sep 329 | << opt_acl << sep << opt_add << " " << sep << opt_remove 330 | << " |" << sep << opt_remove_all << "\n"; 331 | throw std::runtime_error("Wrong usage"); 332 | } 333 | 334 | void tbtadm::Controller::devices() 335 | { 336 | if (!sysfsDeviceExists()) 337 | { 338 | return; 339 | } 340 | 341 | m_sl = findSL(); 342 | 343 | // Find and print devices 344 | for (auto& dir : fs::directory_iterator(sysfsDevicesPath)) 345 | { 346 | if (!is_directory(dir)) 347 | { 348 | continue; 349 | } 350 | if (!isDevice(dir.path())) 351 | { 352 | continue; 353 | } 354 | 355 | bool authorized = stoi(readAndTrim(dir.path() / authorizedFilename)); 356 | 357 | auto inACL = [sl = m_sl] (const auto& dir) 358 | { 359 | auto aclDir = acltree / readAndTrim(dir.path() / uniqueIDFilename); 360 | 361 | if (!fs::exists(aclDir)) 362 | { 363 | return "not in ACL"; 364 | } 365 | if (sl == 2 && !fs::exists(aclDir / keyFilename)) 366 | { 367 | return "not in ACL (no key)"; 368 | } 369 | return "in ACL"; 370 | }; 371 | 372 | // TODO: better formatting 373 | const auto routeString = dir.path().filename().string(); 374 | 375 | Highlight highlight(m_out, authorized ? green : normal); 376 | 377 | m_out << routeString << '\t' << readVendor(dir.path() / vendorFilename) 378 | << '\t' << readDevice(dir.path() / deviceFilename) 379 | << '\t' << (authorized ? "authorized" : "non-authorized") 380 | << '\t' << inACL(dir) << '\n'; 381 | } 382 | } 383 | 384 | void tbtadm::Controller::peers() 385 | { 386 | if (!sysfsDeviceExists()) 387 | { 388 | return; 389 | } 390 | 391 | for (auto& dir : fs::directory_iterator(sysfsDevicesPath)) 392 | { 393 | if (!is_directory(dir)) 394 | { 395 | continue; 396 | } 397 | if (!isXDomain(dir.path())) 398 | { 399 | continue; 400 | } 401 | 402 | chdir(dir.path()); 403 | 404 | // TODO: better formatting 405 | const auto routeString = dir.path().filename().string(); 406 | 407 | m_out << routeString << '\t' << readVendor(vendorFilename) << '\t' 408 | << readDevice(deviceFilename) << std::endl; 409 | } 410 | } 411 | 412 | struct tbtadm::Controller::ControllerInTree 413 | { 414 | ControllerInTree(std::vector&& desc) : m_desc(std::move(desc)) 415 | { 416 | } 417 | std::vector m_desc; 418 | std::map m_children; 419 | }; 420 | 421 | void tbtadm::Controller::topology() 422 | { 423 | std::map controllers; 424 | 425 | if (!sysfsDeviceExists()) 426 | { 427 | return; 428 | } 429 | 430 | for (auto& dir : fs::directory_iterator(sysfsDevicesPath)) 431 | { 432 | if (!is_directory(dir)) 433 | { 434 | continue; 435 | } 436 | auto p = dir.path(); 437 | auto routeString = p.filename().string(); 438 | if (isHost(routeString)) 439 | { 440 | auto num = routeString[0]; 441 | auto security = p.parent_path() / (domain + num) / securityFilename; 442 | m_sl = slMap.find(readAndTrim(security))->second.num; 443 | std::vector desc; 444 | desc.emplace_back("Controller "s + num + '\n'); 445 | desc.emplace_back("Name: " + readDevice(p / deviceFilename) + ", " 446 | + readVendor(p / vendorFilename) 447 | + '\n'); 448 | desc.emplace_back("Security level: " 449 | + slMap.find(readAndTrim(security))->second.desc 450 | + '\n'); 451 | auto i = controllers.emplace(num, std::move(desc)).first; 452 | createTree(i->second, dir.path()); 453 | } 454 | } 455 | 456 | std::string indentation; 457 | for (const auto& host : controllers) 458 | { 459 | auto last = host.first == controllers.rbegin()->first; 460 | m_out << host.second.m_desc[0]; 461 | 462 | indentation = last ? indentLast : indent; 463 | 464 | printDetails( 465 | host.second.m_children.empty(), indentation, host.second.m_desc); 466 | printTree(indentation, host.second.m_children); 467 | } 468 | } 469 | 470 | void tbtadm::Controller::createTree(ControllerInTree& controller, 471 | const fs::path& path) 472 | { 473 | auto authorized = [](const auto& path) -> std::string { 474 | return stoi(readAndTrim(path / authorizedFilename)) ? "Yes" : "No"; 475 | }; 476 | auto inACL = [sl = m_sl](const auto& path)->std::string 477 | { 478 | auto aclDir = acltree / readAndTrim(path / uniqueIDFilename); 479 | if (!fs::exists(aclDir)) 480 | { 481 | return "No"; 482 | } 483 | if (sl == 2 && !fs::exists(aclDir / keyFilename)) 484 | { 485 | return "No (no key)"; 486 | } 487 | return "Yes"; 488 | }; 489 | 490 | for (auto& dir : fs::directory_iterator(path)) 491 | { 492 | if (!is_directory(dir)) 493 | { 494 | continue; 495 | } 496 | auto p = dir.path(); 497 | auto routeString = p.filename().string(); 498 | std::vector desc; 499 | 500 | if (isDevice(p)) 501 | { 502 | desc.emplace_back(readDevice(p / deviceFilename) + ", " 503 | + readVendor(p / vendorFilename) 504 | + "\n"); 505 | desc.emplace_back("Route-string: " + routeString + "\n"); 506 | desc.emplace_back("Authorized: " + authorized(p) + "\n"); 507 | desc.emplace_back("In ACL: " + inACL(p) + "\n"); 508 | desc.emplace_back("UUID: " + readAndTrim(p / uniqueIDFilename) 509 | + "\n"); 510 | } 511 | else if (isXDomain(p)) 512 | { 513 | desc.emplace_back(readDevice(p / deviceFilename) + ", " 514 | + readVendor(p / vendorFilename) 515 | + "\n"); 516 | desc.emplace_back("Route-string: " + routeString + "\n"); 517 | desc.emplace_back("UUID: " + readAndTrim(p / uniqueIDFilename) 518 | + "\n"); 519 | } 520 | else 521 | { 522 | continue; 523 | } 524 | 525 | auto i = 526 | controller.m_children.emplace(routeString, std::move(desc)).first; 527 | createTree(i->second, dir.path()); 528 | } 529 | } 530 | 531 | void tbtadm::Controller::printTree( 532 | std::string& indentation, 533 | const std::map& map) 534 | { 535 | for (const auto& device : map) 536 | { 537 | auto last = device.first == map.rbegin()->first; 538 | m_out << indentation << SYMBOL_PIPE << "\n"; 539 | m_out << indentation << (last ? SYMBOL_L : SYMBOL_PLUS) 540 | << device.second.m_desc[0]; 541 | indentation += last ? indentLast : indent; 542 | printDetails(device.second.m_children.empty(), 543 | indentation, 544 | device.second.m_desc); 545 | printTree(indentation, device.second.m_children); 546 | indentation.resize(indentation.size() - indentLength); 547 | } 548 | } 549 | 550 | void tbtadm::Controller::printDetails(bool last, 551 | std::string& indentation, 552 | const std::vector& details) 553 | { 554 | m_out << indentation << (last ? SYMBOL_L : SYMBOL_PLUS) 555 | << "Details:\n"; 556 | 557 | indentation += last ? indentLast : indent; 558 | 559 | size_t detailsSize = details.size(); 560 | for (size_t i = 1; i < detailsSize; ++i) 561 | { 562 | if (i == detailsSize - 1) 563 | m_out << indentation << SYMBOL_L << details[i]; 564 | else 565 | m_out << indentation << SYMBOL_PLUS << details[i]; 566 | } 567 | indentation.resize(indentation.size() - indent.size()); 568 | } 569 | 570 | void tbtadm::Controller::approveAll() 571 | { 572 | if (!sysfsDeviceExists()) 573 | { 574 | return; 575 | } 576 | 577 | for (auto& dir : fs::directory_iterator(sysfsDevicesPath)) 578 | { 579 | if (!is_directory(dir)) 580 | { 581 | continue; 582 | } 583 | if (!isDomain(dir.path())) 584 | { 585 | continue; 586 | } 587 | m_out << "Found domain " << dir << '\n'; 588 | auto domainNum = dir.path().filename().string().substr(domain.size()); 589 | m_sl = 590 | slMap.find(readAndTrim(dir.path() / securityFilename))->second.num; 591 | switch (m_sl) 592 | { 593 | case SECURITY_LEVEL_USER: 594 | case SECURITY_LEVEL_SECURE: 595 | break; 596 | case SECURITY_LEVEL_NONE: 597 | case SECURITY_LEVEL_DPONLY: 598 | m_out << "Approval not relevant in SL" << m_sl << '\n'; 599 | return; 600 | default: 601 | m_out << "Unknown Security level " << m_sl << '\n'; 602 | return; 603 | } 604 | approveAll(dir / (domainNum + hostRouteString)); 605 | } 606 | } 607 | 608 | void tbtadm::Controller::approveAll(const fs::path& dir) 609 | { 610 | for (auto& child : fs::directory_iterator(dir)) 611 | { 612 | if (!is_directory(child)) 613 | { 614 | continue; 615 | } 616 | if (fs::exists(child / authorizedFilename)) 617 | { 618 | m_out << "Found child " << child << '\n'; 619 | approve(child); 620 | approveAll(child); 621 | } 622 | } 623 | } 624 | 625 | // TODO: move to tbtadm-helper 626 | void tbtadm::Controller::approve(const fs::path& dir) try 627 | { 628 | m_out << "Authorizing " << dir << '\n'; 629 | 630 | File authorized(dir / authorizedFilename, File::Mode::Read); 631 | if (std::stoi(authorized.read())) 632 | { 633 | m_out << "Already authorized\n"; 634 | return; 635 | } 636 | 637 | if (!m_once) 638 | { 639 | addToACL(dir); 640 | } 641 | 642 | std::ostringstream keyStream; 643 | if (m_sl == SECURITY_LEVEL_SECURE && !m_once) 644 | { 645 | std::default_random_engine eng(std::random_device{}()); 646 | std::uniform_int_distribution<> dist(0, 0xF); 647 | keyStream << std::hex; 648 | std::generate_n(std::ostream_iterator(keyStream), 64, [&] { 649 | return dist(eng); 650 | }); 651 | 652 | File key(dir / keyFilename, File::Mode::Write); 653 | key << keyStream.str(); 654 | } 655 | 656 | authorized = File(dir / authorizedFilename, File::Mode::Write); 657 | authorized << 1; 658 | 659 | m_out << "Authorized\n"; 660 | if (m_sl == SECURITY_LEVEL_SECURE && !m_once) 661 | { 662 | File keyACL(acltree / readAndTrim(dir / uniqueIDFilename) / keyFilename, 663 | File::Mode::Write, 664 | O_CREAT, 665 | S_IRUSR); 666 | keyACL << keyStream.str(); 667 | m_out << "Key saved in ACL\n"; 668 | } 669 | } 670 | catch (std::system_error& e) 671 | { 672 | m_err << e.code() << ' ' << e.what() << '\n'; 673 | } 674 | catch (std::exception& e) 675 | { 676 | m_err << "Exception: " << e.what() << '\n'; 677 | } 678 | catch (...) 679 | { 680 | m_err << "Unknown exception\n"; 681 | } 682 | 683 | void tbtadm::Controller::addToACL(const fs::path& dir) 684 | { 685 | auto acl = acltree / readAndTrim(dir / uniqueIDFilename); 686 | if (fs::exists(acl)) 687 | { 688 | m_out << "Already in ACL\n"; 689 | return; 690 | } 691 | 692 | fs::create_directories(acl); 693 | fs::copy(dir / vendorFilename, acl / vendorFilename); 694 | fs::copy(dir / deviceFilename, acl / deviceFilename); 695 | 696 | m_out << "Added to ACL\n"; 697 | } 698 | 699 | void tbtadm::Controller::acl() 700 | { 701 | if (!fs::exists(acltree) || fs::is_empty(acltree)) 702 | { 703 | m_out << "ACL is empty\n"; 704 | return; 705 | } 706 | 707 | // Get UUID of all connected devices 708 | std::map uuids; 709 | if (fs::exists(sysfsDevicesPath)) 710 | { 711 | for (auto& dir : fs::directory_iterator(sysfsDevicesPath)) 712 | { 713 | if (!is_directory(dir)) 714 | { 715 | continue; 716 | } 717 | if (!isDevice(dir.path())) 718 | { 719 | continue; 720 | } 721 | File authorizedFile(dir.path() / authorizedFilename, 722 | File::Mode::Read); 723 | bool authorized = std::stoi(authorizedFile.read()); 724 | std::string uuid(readAndTrim(dir.path() / uniqueIDFilename)); 725 | uuids.insert(std::make_pair(uuid, authorized)); 726 | } 727 | m_sl = findSL(); 728 | } 729 | 730 | // Print ACL 731 | bool doNoKey = false; 732 | for (auto& dir : fs::directory_iterator(acltree)) 733 | { 734 | const auto p = dir.path(); 735 | if (m_sl != SECURITY_LEVEL_SECURE || fs::exists(p / keyFilename)) 736 | { 737 | const auto uuid = p.filename().string(); 738 | auto entry = uuids.find(uuid); 739 | bool connected = entry != uuids.end(); 740 | std::string color = normal; 741 | 742 | if (connected) 743 | color = entry->second ? green : yellow; 744 | 745 | Highlight highlight(m_out, color); 746 | 747 | m_out << uuid << '\t' << readVendor(p / vendorFilename) << '\t' 748 | << readDevice(p / deviceFilename) << '\t' 749 | << (connected ? "connected" : "not connected") << "\n"; 750 | } 751 | else 752 | { 753 | doNoKey = true; 754 | } 755 | } 756 | if (doNoKey) 757 | { 758 | m_out << "\nACL entries with no key (not for current security mode):\n"; 759 | for (auto& dir : fs::directory_iterator(acltree)) 760 | { 761 | const auto p = dir.path(); 762 | if (!fs::exists(p / keyFilename)) 763 | { 764 | const auto uuid = p.filename().string(); 765 | auto entry = uuids.find(uuid); 766 | bool connected = entry != uuids.end(); 767 | std::string color = normal; 768 | 769 | if (connected) 770 | color = entry->second ? green : yellow; 771 | 772 | Highlight highlight(m_out, color); 773 | 774 | m_out << uuid << '\t' << readVendor(p / vendorFilename) << '\t' 775 | << readDevice(p / deviceFilename) << '\t' 776 | << (connected ? "connected" : "not connected") << "\n"; 777 | } 778 | } 779 | } 780 | } 781 | 782 | void tbtadm::Controller::add(const fs::path& dir) 783 | { 784 | switch (m_sl) 785 | { 786 | case SECURITY_LEVEL_SECURE: 787 | m_out << "Adding to ACL on SL2 must be done together with device " 788 | "approval\n"; 789 | return; 790 | case SECURITY_LEVEL_NONE: 791 | case SECURITY_LEVEL_DPONLY: 792 | m_out << "Adding to ACL is not relevant in SL" << m_sl << '\n'; 793 | return; 794 | case SECURITY_LEVEL_USER: 795 | break; 796 | default: 797 | m_out << "Unknown Security level " << m_sl << '\n'; 798 | return; 799 | } 800 | 801 | addToACL(dir); 802 | } 803 | 804 | // TODO: move to tbtadm-helper 805 | void tbtadm::Controller::remove(std::string uuid) 806 | { 807 | // Identify route-string argument and replace it with the UUID 808 | if (isRouteString(uuid)) 809 | { 810 | uuid = readAndTrim(sysfsDevicesPath / uuid / uniqueIDFilename); 811 | } 812 | 813 | auto acl = acltree / uuid; 814 | if (!fs::exists(acl)) 815 | { 816 | m_out << "ACL entry doesn't exist\n"; 817 | } 818 | fs::remove_all(acl); 819 | } 820 | 821 | // TODO: move to tbtadm-helper 822 | void tbtadm::Controller::removeAll() 823 | { 824 | if (!fs::exists(acltree) || fs::is_empty(acltree)) 825 | { 826 | m_out << "ACL is empty\n"; 827 | return; 828 | } 829 | auto count = 830 | std::count_if(fs::directory_iterator(acltree), 831 | {}, 832 | [](const auto& dir) { return fs::is_directory(dir); }); 833 | fs::remove_all(acltree); 834 | m_out << count << " entries removed\n"; 835 | } 836 | --------------------------------------------------------------------------------