pax_global_header00006660000000000000000000000064141745305100014512gustar00rootroot0000000000000052 comment=9a9d0eafd14a3c8b5c03516cb8a9c6199ac0d4b9 bolt-0.9.2/000077500000000000000000000000001417453051000124625ustar00rootroot00000000000000bolt-0.9.2/.devcontainer/000077500000000000000000000000001417453051000152215ustar00rootroot00000000000000bolt-0.9.2/.devcontainer/Dockerfile000066400000000000000000000015541417453051000172200ustar00rootroot00000000000000FROM fedora:latest RUN dnf install -y \ asciidoc \ bat \ clang-analyzer \ ccls \ codespell \ findutils \ fish \ fpaste \ gcc \ git \ glib2-devel \ glibc-langpack-en \ gtk-doc \ hostname \ ipython3 \ jq \ lcov \ libgudev-devel \ lsof \ make \ meson \ packit \ polkit-devel \ pylint \ python3 \ python3-autopep8 \ python3-dbus \ python3-dbusmock \ python3-devel \ python3-docutils \ python3-enchant \ python3-gobject \ python3-jsonschema \ python3-pip \ python3-pycodestyle \ python3-pylint \ python3-pytest \ python3-pyyaml \ redhat-rpm-config \ rpm-build \ rpmlint \ skopeo \ strace \ systemd-devel \ the_silver_searcher \ tree \ umockdev-devel \ uncrustify WORKDIR /workspaces/bolt bolt-0.9.2/.devcontainer/devcontainer.json000066400000000000000000000017611417453051000206020ustar00rootroot00000000000000{ "name": "bolt", "build": { "dockerfile": "Dockerfile", "context": "..", }, "mounts": [ "source=bold-share,target=/root/.local/share,type=volume" ], "runArgs": [ "--privileged" ], "settings": { "C_Cpp.autocomplete": "Disabled", "C_Cpp.formatting": "Disabled", "C_Cpp.errorSquiggles": "Disabled", "C_Cpp.intelliSenseEngine": "Disabled", "terminal.integrated.shell.linux": "/bin/fish", "python.pythonPath": "/usr/bin/python", "python.linting.enabled": true, "python.linting.pylintEnabled": true, "python.testing.unittestEnabled": false, "python.testing.nosetestsEnabled": false, "python.testing.pyTestEnabled": true, "python.testing.pyTestArgs": [ "test" ] }, "extensions": [ "asabil.meson", "editorconfig.editorconfig", "ccls-project.ccls", "laurenttreguier.rpm-spec", "ms-python.python", "ms-python.vscode-pylance", "ms-vscode.cpptools", "ms-vscode-remote.remote-containers", ] } bolt-0.9.2/.editorconfig000066400000000000000000000004271417453051000151420ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 [*.{c,h}] indent_style = space indent_size = 2 indent_brace_style = gnu [meson.build] indent_style = space indent_size = 2 [.md] indent_style = space indent_size = 2 bolt-0.9.2/.flake8000066400000000000000000000000371417453051000136350ustar00rootroot00000000000000[flake8] max-line-length = 120 bolt-0.9.2/.gitignore000066400000000000000000000003171417453051000144530ustar00rootroot00000000000000 # files *~ *.log *.xz # directories build/ dist/ # coverity cov-int/ coverity/ # IDEs and related .ccls-cache/ .cquery_cached_index/ compile_commands.json .idea .gdb_history .vscode/ # emacs TAGS \#*\# bolt-0.9.2/.gitlab-ci.yml000066400000000000000000000022771417453051000151260ustar00rootroot00000000000000image: docker:latest services: - docker:dind .build-template: &build script: - docker build -t bolt-$OS -f ./contrib/Dockerfile-$OS . - mkdir build-$OS - docker run --cap-drop=dac_override -e -t -v `pwd`:/src -v `pwd`/build-$OS:/build bolt-$OS ./contrib/docker-build.sh artifacts: paths: - build-$OS/meson-logs expire_in: 1 week fedora: stage: build variables: OS: fedora <<: *build arch: stage: build allow_failure: true variables: OS: arch <<: *build debian: stage: build variables: OS: debian <<: *build alpine: stage: build variables: OS: alpine <<: *build coverity: stage: build only: refs: - schedules variables: - $COVERITY_TOKEN && $COVERITY_EMAIL script: - mkdir build-coverity - docker build --build-arg ORG=gicmo --build-arg PROJECT=bolt --build-arg TOKEN=$COVERITY_TOKEN -t bolt-coverity -f ./contrib/Dockerfile-coverity . - docker run --rm -e COVERITY_TOKEN=$COVERITY_TOKEN -e COVERITY_EMAIL=$COVERITY_EMAIL -t -v `pwd`:/src:Z -v `pwd`/build-coverity:/build:Z bolt-coverity ./contrib/coverity.sh artifacts: paths: - build-coverity/cov-int/build-log.txt expire_in: 1 week bolt-0.9.2/.pylintrc000066400000000000000000000002501417453051000143240ustar00rootroot00000000000000[MASTER] disable=missing-docstring,invalid-name,duplicate-code,superfluous-parens,too-many-locals,attribute-defined-outside-init,too-many-arguments max-line-length=120 bolt-0.9.2/.travis.yml000066400000000000000000000005731417453051000146000ustar00rootroot00000000000000language: c sudo: required dist: bionic services: - docker env: - OS=alpine - OS=debian - OS=arch - OS=fedora install: - docker build -t bolt-$OS -f ./contrib/Dockerfile-$OS . script: - mkdir build-$OS - sudo chown $(id -u):root build-$OS - docker run --cap-drop=dac_override -e -t -v `pwd`:/src -v `pwd`/build-$OS:/build bolt-$OS ./contrib/docker-build.sh bolt-0.9.2/BUGS.md000066400000000000000000000007651417453051000135540ustar00rootroot00000000000000Bugs ==== Bugs are tracked at [fd.o's gitlab][gitlab issues]. Debugging ========= The daemon can be run in verbose mode with the command line option `-v`. This should normally not be necessary, except when debugging the DBus layer of boltd (the verbose output is indeed very verbose). To replace the currently running daemon and run a new instance of it in the foreground, launch the daemon with `--replace`: boltd --replace [gitlab issues]: https://gitlab.freedesktop.org/bolt/bolt/issues bolt-0.9.2/CHANGELOG.md000066400000000000000000000226751417453051000143070ustar00rootroot00000000000000Version 0.9.2 ------------- _Please get along_ Released: 2022-01-27 * This release is compatible with umockdev >= 0.16.3; there was a change in umockdev that made our test fail with it, since both our test and umockev were trying to create the same directorires. bolt now allows for the dir to already exist. * The license for `90-bolt.rules` has changed from `GPL-2.1+`, which does not exist and was probably was confused with `LGPL-2.1+`, to `GPL 2.0+`. * Documentation has been updated and spelling mistakes fixed. * Various improvements for continuous integration. * The minimum required version of meson has been bumped to 0.46.0. Version 0.9.1 ------------- _Unstable icy waters_ Released: 2020-11-30 * Bug fixes for integrated thunderbolt controllers: On Ice Lake, the Thunderbolt 3 i/o subsystem is fully integrated into the die. As a side effect it does not have a DROM, which means the host udev device does not have the device and vendor name and id attributes. Additionally the `unique_id` of said host controller changes with every boot, which breaks one of the fundamental assumptions in `boltd`. Therefore a number of bug fixes were necessary to properly support this new architecture: - Don't store domains where uuids change across reboots [!220] - Fixes for the journal and the domain's acl-log [!221] - Version the store and use that to clean up stale domains once [!226, !231] - Host identification for embedded thunderbolt controllers [!233] * Various other small bug fixes and memory leak fixes. Version 0.9 ----------- _Four comes after Three_ Released: 2020-06-15 * New Features: - Add 'Generation' attribute for the Manager [!197] - Ability to change the policy of a stored device [!202] - The BootACL Domain property is now writable [!184] - Support for systemd's service watchdog [!185] - Expose Link Speed sysfs attributes [!214] * Improvements: - boltclt: show timestamps in 'monitor' call [!208] - Persist the host device [!194] * Bug fixes: - Fix a flaky test [!217, #161] - Plug small memory leaks in error conditions [!217] - Ignore spurious wakeup device uevents for probing [!209] - Preserve keystate when updating devices [!192] Version 0.8 ----------- _I owe it to the MM U!_ Released: 2019-06-14 * New Features: - **IOMMU support**: adapt behavior iommu support is present and active [#128] - automatically enroll new devices with the new `iommu` policy when iommu is active - automatically authorize devices with the `iommu` policy if iommu is active - `boltctl config` command to describe, get and set global, device and domain properties. - Chain authorization and enrollment via `boltctl {enroll, authorize} --chain` [!153, !154] - `bolt-mock` script for interactively testing `boltd` [!152] * Improvements: - Automatically import devices that were authorized at boot [#137] - Make tests installable [#140] - Honour `STATE_DIRECTORY` [!159] and `RUNTIME_DIRECTORY` [!161] - Profiling support via gprof [!168] * Bug fixes: - Better handling of random data generation [#132, !165] - Fix double free in case of client creation failure [!148] - Fix invalid format string in warning [!14] * NB for packagers: - The dbus configuration is now installed in `$datadir/dbus-1/system.d` instead of `$sysconfdir` [!177]. - To install tests, configure with `-Dinstall-tests=true`. Version 0.7 ----------- _The Known Unknowns_ Released: 2019-01-01 * Features: - announce status to systemd via sd_notify (using a simple custom implementation) [!143] * Bug fixes: - properly update global security level status [#131 via !141] - adapt to `systemd` 240 not sending `bind`/`unbind` uevents [#133 via !145] - fix compilation on musl [#126 via !140] - daemon: use `g_unix_signal_source…` to catch signals [#127, #129 via !138] * Improvements - precondition checks cleanup and completion [#124 via !139] - error cleanup [#125, !142] - fix some leaks and issues uncovered by coverity [!144] Version 0.6 ----------- _Make the firmware do it!_ Released: 2018-11-28 * New Features: - **pre-boot access control list, aka. `BootACL`** support [!119] - domains objects are now persistent - new `Uid` (dbus) / `uid` (object) property derived from the uuid of the device representing the root switch - `sysfs` and `id` attribute will be set/unset on connects and disconnects - domains are now stored in the boltd database - domains got the `BootACL` (dbus) / `bootacl` (object) property - uuids can be added, removed or set in batch - when domain is *online*: changes are written to the sysfs `boot_acl` attribute directly - when domain is *offline*: changes are written to a journal and then reapplied in order when the domain is connected - newly enrolled devices get added to all bootacls of all domains *if* the `policy` is `BOLT_POLICY_AUTO` - removed devices get deleted from all bootacls of all domains - `boltacl domain` command will show the bootacl slots and their content - `boltctl` gained the `-U, --uuid` option, to control how uuids are printed [!124] * Improvements and fixes: - Testing [!127] - The test coverage increased to `84.80%` overall and to `90.0%` for the `boltd` source - Coverage is reported for merge requests via the fedora ci image [!126] - `boltctl` is now included in the tests [!132] - Fedora 29 is used for the fedora ci image - Bugs and robustness: - The device state is verified in `Device.Authorize` [!120] - Handle empty 'keys' sysfs device attribute [!129] - Properly adjust policies when enrolling already authorized devices [!136] - Fix potential crash when logging assertions `g_return_if_fail` [!121] Version 0.5 ----------- _You've got the Power_ Released: 2018-09-28 * New Features: - Force-Power DBus API ⚡(!101) - A new interface to boltd to control the (force) power mechanism (#106) - Switch off power with a delay so we don't run into races (#104) - Add representation of thunderbolt domains
This is a preparation for the boot acl support - Authorizing devices, after upgrading from `USER` to `SECURE` security level, will lead to key upgrades (!107) - Connection and Authorization times are now stored (!105) - Systemd dependency is now optional (!106, !103) - Company and brand names are cleaned up for the display name (#102) * Bug fixes and cleanups: - Emit proper notification for security-level property changes (!100) - Auto generate the object path for BoltDevice (!102) * NB for packagers: - `-Ddb-path` is **DEPRECATED**, use `-Ddb-name` instead (!113) - meson >= 0.44.0 is required. - systemd unit files got updated: - `After=polkit.service` (!116) - Use systemd for runtime and state directory management (!113) - Sandbox is tightened (!97) Version 0.4 ----------- _The Race Is Over_ Released: 2018-05-28 * New features: - auto import of devices authorized during boot [!90] - allow enrolling of already authorized devices, i.e. importing of devices [!86] - label new devices and detect duplicates [!91] * Be more robust: - Handle NULL errors in logging code better [!89] - Properly handle empty device database entries [!87] - Better authentication errors and logging [!85] - More tests * Internal changes: - Make sure we don't miss device status changes [!82] - Rework property change notification dispatching [!83] Version 0.3 ----------- _Capture The Flags_ Released: 2018-05-28 * Prepare for upcoming kernel changes: - Support for `usbonly` (SL4) security level (#75) - Support for `boot` sysfs device attribute (#76) * DBus API changes: - `BoltStatus` was split (#81), so that: - `Device.Status` does not report `authorized-xxx` anymore - `Device.AuthFlags` added to indicate auth details, e.g. `secure`, `nopci`, `boot`, `nokey` (#76) - `BoltSecurity` and thus `Manager.SecurityLevel` can report `usbonly` (#75) * client/boltctl: - async versions for many function calls - more efficient getters, resulting in reduced allocations - boltctl reports `Device.AuthFlags` - boltctl prints more and better version info via `boltctl monitor` * Other bugfixes and improvements include: - more robust flags/enum conversion Version 0.2 ----------- _I broke the Bus_ Released: 2018-03-06 Lots of changes, the most significant: - database location moved (now in `/var/lib/boltd`) - **⚠** devices enrolled with bolt 0.1 need to be re-enrolled (or the database moved from the old location) - DBus API changed (lots of strings) - Enums are transmitted as strings - `Device.Security` property is gone; replaced by `authorized-dponly` status and `Manager.SecurityLevel` ( #37, #38, #62) - Various timestamps got added: `Device.ConnectTime`, `Device.StoreTime` and `Device.AuthorizeTime` (#46 #57) - `Device.Label` (readwrite) was added so devices can be given custom names (#46) - `Device.Type` added, to differentiate between host and peripherals - `Manager.AuthMode` (readwrite) was added to control (auto) authorization (#48) Other bugfixes and improvements include: - Ensure we get a `DeviceAdded` signal on startup (#58) - Support for legacy devices that have no key sysfs attribute (#67) - Use structured logging and avoid printing UUIDs in non-debug log code (#36 #60) - Other internal restructuring for cleaner code (#43) Version 0.1 ----------- _Accidentally Working_ Released: 2017-12-13 * functional daemon that can authorize enroll and authorize devices * `boltctl` command to interact with the daemon bolt-0.9.2/COPYING000066400000000000000000000636421417453051000135300ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! bolt-0.9.2/HACKING.md000066400000000000000000000046721417453051000140610ustar00rootroot00000000000000Patches ======= Patches should be submitted in the form of merge requests at [gitlab][gitlab]. Coding style ============ The style is codified via the supplied `sscripts/uncrustify.cfg` config. It can be automatically formatted by the `uncrustify` target, i.e. by invoking `ninja -C uncrustify`. Make sure to format the source code before submitting pull requests. Testing ======= To run the test suite in verbose mode: meson test -C build --verbose To run `boltd` from within valgrind for the integration tests set the environment variable `VALGRIND`. A [suppression file][valgrind] can be specified via that variable as well (n.b. for meson the path must be relative to the build directory): VALGRIND=../bolt.supp meson test -C build --verbose Coverage -------- To analyze the current code coverage either `lcov` or `gcovr` need to be installed. Support must also be enabled during configure time: meson -Db_coverage=true This should enable the general `coverage` target as well as the `coverage-{text, html, xml}` targets: ninja -C coverage To manually invoke `gcovr` and exclude the `cli` directory use: gcovr -r -e cli -s Address Sanitizer ================= The Address Sanitizer can be used to detect memory errors, i.e. memory leaks and undefined behavior. Use `clang` for this, since there the needed library (`libasan`) is dynamically loaded, instead of pre-loaded via `LD_PRELOAD` when using `gcc`, which conflicts with our pre-load needed for `umockdev`. env CC=clang meson -Db_sanitize=address,undefined . ninja -C test NB: There might be a warning that `b_lundef` is needed as well. It seems to work just fine right now without it. Static analysis =============== The clang static analyzer can be run locally via: ninja -C scan-build Coverity -------- Bolt is registered with [coverity][coverity]. To submit a local build, execute the following commands (the `cov-build` [build tool][cov-build] must be in `PATH`) from the source directory: CC=gcc CXX=gcc meson -Dcoverity=true coverity cov-build --dir cov-int ninja -C coverity tar caf bolt.xz cov-int Upload the `bolt.xz` file to coverity for analysis. Fix defects. Profit. [gitlab]: https://gitlab.freedesktop.org/bolt/bolt [coverity]: https://scan.coverity.com/projects/bolt [cov-build]: https://scan.coverity.com/download [valgrind]: https://gist.github.com/gicmo/327dad149fcb386ac7f59e279b8ba322 bolt-0.9.2/INSTALL.md000066400000000000000000000011711417453051000141120ustar00rootroot00000000000000BUILDING ======== The [meson][meson] build system is used to configure and compile bolt. meson build # configure bolt, use build as buildir ninja -C build # compile it ninja -C build test # run the tests NB: `boltd` comes with configuration files for dbus and PolicyKit that need to be installed to the proper locations. It is probably a good idea to manually specify them with the correct values for the current distribution. This can be done by passing the corresponding options to meson: --sysconfdir=/etc --localstatedir=/var --sharedstatedir=/var/lib [meson]: http://mesonbuild.com/ bolt-0.9.2/README.md000066400000000000000000000052441417453051000137460ustar00rootroot00000000000000bolt ==== Userspace system daemon to enable security levels for *Thunderbolt™* on GNU/Linux®. Introduction ------------ Thunderbolt™ is the brand name of a hardware interface developed by Intel® that allows the connection of external peripherals to a computer. Devices connected via Thunderbolt can be DMA masters and thus read system memory without interference of the operating system (or even the CPU). Version 3 of the interface introduced 5 different security levels, in order to mitigate the aforementioned security risk that connected devices pose to the system. The security level is set by the system firmware. The five security levels are: * `none`: Security disabled, all devices will fully functional on connect. * `dponly`: Only pass the display-port stream through to the connected device. * `user`: Connected devices need to be manually authorized by the user. * `secure`: As 'user', but also challenge the device with a secret key to verify its identity. * `usbonly`: One PCIe tunnel is created to a usb controller in a thunderbolt dock; no other downstream PCIe tunnels are authorized (needs 4.17 kernel and recent hardware). The Linux kernel, starting with version 4.13, provides an interface via sysfs that enables userspace query the security level, the status of connected devices and, most importantly, to authorize devices, if the security level demands it. boltd - the system daemon ------------------------- The core of bolt is a system daemon (`boltd`) that interfaces with sysfs and exposes devices via D-Bus to clients. It also has a database of previously authorized devices (and their keys) and will, depending on the policy set for the individual devices, automatically authorize newly connected devices without user interaction. The daemon supports syncing the devices database with the pre-boot access control list firmware feature. It also adapts its behavior when iommu support is detected. boltctl - command line client ----------------------------- The `boltctl` command line can be used to manage thunderbolt devices via `boltd`. It can list devices, monitor changes and initiate authorization of devices. Installation ============ The [meson][meson] build system is used to configure and compile bolt. meson build # configure bolt, use build as buildir ninja -C build # compile it ninja -C build test # run the tests See [INSTALL][install] for more information, [BUGS][bugs] for how to file issues and [HACKING][hacking] how to contribute. [meson]: http://mesonbuild.com/ [install]: INSTALL.md [bugs]: BUGS.md [hacking]: HACKING.md bolt-0.9.2/boltd/000077500000000000000000000000001417453051000135665ustar00rootroot00000000000000bolt-0.9.2/boltd/bolt-auth.c000066400000000000000000000235051417453051000156360ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-auth.h" #include "bolt-device.h" #include "bolt-error.h" #include "bolt-io.h" #include "bolt-log.h" #include "bolt-store.h" #include static void async_result_iface_init (GAsyncResultIface *iface); struct _BoltAuth { GObject object; GObject *origin; BoltSecurity level; BoltKey *key; /* the device */ BoltDevice *dev; /* result */ GError *error; /* memory for enrollment */ BoltPolicy policy; }; enum { PROP_0, PROP_ORIGIN, PROP_LEVEL, PROP_KEY, PROP_DEVICE, PROP_ERROR, PROP_POLICY, PROP_LAST }; static GParamSpec *props[PROP_LAST] = { NULL, }; G_DEFINE_TYPE_WITH_CODE (BoltAuth, bolt_auth, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, async_result_iface_init)); static void bolt_auth_finalize (GObject *object) { BoltAuth *auth = BOLT_AUTH (object); g_clear_object (&auth->origin); g_clear_object (&auth->key); g_clear_object (&auth->dev); g_clear_error (&auth->error); G_OBJECT_CLASS (bolt_auth_parent_class)->finalize (object); } static void bolt_auth_init (BoltAuth *auth) { } static void bolt_auth_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltAuth *auth = BOLT_AUTH (object); switch (prop_id) { case PROP_ORIGIN: g_value_set_object (value, auth->origin); break; case PROP_LEVEL: g_value_set_enum (value, auth->level); break; case PROP_KEY: g_value_set_object (value, auth->key); break; case PROP_DEVICE: g_value_set_object (value, auth->dev); break; case PROP_POLICY: g_value_set_enum (value, auth->policy); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_auth_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltAuth *auth = BOLT_AUTH (object); switch (prop_id) { case PROP_ORIGIN: auth->origin = g_value_dup_object (value); break; case PROP_LEVEL: auth->level = g_value_get_enum (value); break; case PROP_KEY: auth->key = g_value_dup_object (value); break; case PROP_DEVICE: g_return_if_fail (auth->dev == NULL); auth->dev = g_value_dup_object (value); break; case PROP_POLICY: auth->policy = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_auth_class_init (BoltAuthClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = bolt_auth_finalize; gobject_class->get_property = bolt_auth_get_property; gobject_class->set_property = bolt_auth_set_property; props[PROP_ORIGIN] = g_param_spec_object ("origin", NULL, NULL, G_TYPE_OBJECT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NICK); props[PROP_LEVEL] = g_param_spec_enum ("level", NULL, NULL, BOLT_TYPE_SECURITY, BOLT_SECURITY_NONE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NICK); props[PROP_KEY] = g_param_spec_object ("key", NULL, NULL, BOLT_TYPE_KEY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NICK); props[PROP_DEVICE] = g_param_spec_object ("device", NULL, NULL, BOLT_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_STATIC_NICK); props[PROP_ERROR] = g_param_spec_boxed ("error", NULL, NULL, G_TYPE_ERROR, G_PARAM_READWRITE | G_PARAM_STATIC_NICK); props[PROP_POLICY] = g_param_spec_enum ("policy", NULL, NULL, BOLT_TYPE_POLICY, BOLT_POLICY_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); } /* async result methods */ static gpointer async_result_get_user_data (GAsyncResult *res) { g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); return NULL; } static GObject * async_result_get_source_object (GAsyncResult *res) { g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); return G_OBJECT (BOLT_AUTH (res)->dev); } static gboolean async_result_is_tagged (GAsyncResult *res, gpointer source_tag) { g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); return FALSE; } static void async_result_iface_init (GAsyncResultIface *iface) { iface->get_source_object = async_result_get_source_object; iface->get_user_data = async_result_get_user_data; iface->is_tagged = async_result_is_tagged; } /* public methods */ BoltAuth * bolt_auth_new (gpointer origin, BoltSecurity level, BoltKey *key) { BoltAuth *auth; auth = g_object_new (BOLT_TYPE_AUTH, "origin", origin, "level", level, "key", key, NULL); return auth; } void bolt_auth_return_new_error (BoltAuth *auth, GQuark domain, gint code, const char *format, ...) { va_list args; g_return_if_fail (BOLT_IS_AUTH (auth)); va_start (args, format); auth->error = g_error_new_valist (domain, code, format, args); va_end (args); } void bolt_auth_return_error (BoltAuth *auth, GError **error) { g_return_if_fail (BOLT_IS_AUTH (auth)); g_return_if_fail (error != NULL && *error != NULL); g_return_if_fail (auth->error == NULL); auth->error = g_steal_pointer (error); } gboolean bolt_auth_check (BoltAuth *auth, GError **error) { g_return_val_if_fail (BOLT_IS_AUTH (auth), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (auth->error) { g_autoptr(GError) err = g_error_copy (auth->error); return bolt_error_propagate (error, &err); } return TRUE; } BoltDevice * bolt_auth_get_device (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), NULL); return auth->dev; } BoltSecurity bolt_auth_get_level (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), BOLT_SECURITY_UNKNOWN); return auth->level; } BoltKey * bolt_auth_get_key (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), NULL); return auth->key; } BoltKeyState bolt_auth_get_keystate (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), BOLT_KEY_MISSING); return bolt_key_get_state (auth->key); } gboolean bolt_auth_has_key (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), FALSE); return auth->key != NULL; } gpointer bolt_auth_get_origin (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), NULL); return auth->origin; } BoltPolicy bolt_auth_get_policy (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), BOLT_POLICY_UNKNOWN); return auth->policy; } void bolt_auth_set_policy (BoltAuth *auth, BoltPolicy policy) { g_return_if_fail (BOLT_IS_AUTH (auth)); if (auth->policy == policy) return; auth->policy = policy; g_object_notify_by_pspec (G_OBJECT (auth), props[PROP_POLICY]); } BoltStatus bolt_auth_to_status (BoltAuth *auth) { g_return_val_if_fail (BOLT_IS_AUTH (auth), BOLT_STATUS_UNKNOWN); if (auth->error != NULL) return BOLT_STATUS_AUTH_ERROR; switch (auth->level) { case BOLT_SECURITY_SECURE: case BOLT_SECURITY_USER: return BOLT_STATUS_AUTHORIZED; case BOLT_SECURITY_DPONLY: case BOLT_SECURITY_USBONLY: case BOLT_SECURITY_NONE: bolt_bug ("unexpected security in BoltAuth::level: %s", bolt_security_to_string (auth->level)); return BOLT_STATUS_AUTHORIZED; case BOLT_SECURITY_UNKNOWN: bolt_bug ("unknown status in BoltAUth::level"); return BOLT_STATUS_UNKNOWN; } return BOLT_STATUS_UNKNOWN; } BoltAuthFlags bolt_auth_to_flags (BoltAuth *auth, BoltAuthFlags *mask) { BoltKeyState ks; g_return_val_if_fail (BOLT_IS_AUTH (auth), 0); if (mask) *mask = 0; if (auth->error != NULL) return 0; if (auth->level != BOLT_SECURITY_SECURE) return 0; if (mask) *mask = BOLT_AUTH_SECURE; ks = bolt_key_get_state (auth->key); if (ks == BOLT_KEY_NEW) return 0; return BOLT_AUTH_SECURE; } bolt-0.9.2/boltd/bolt-auth.h000066400000000000000000000046761417453051000156530ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-key.h" #include G_BEGIN_DECLS /* forward decl because bolt-device.h include bolt-auth.h */ typedef struct _BoltDevice BoltDevice; #define BOLT_TYPE_AUTH bolt_auth_get_type () G_DECLARE_FINAL_TYPE (BoltAuth, bolt_auth, BOLT, AUTH, GObject); BoltAuth * bolt_auth_new (gpointer origin, BoltSecurity level, BoltKey *key); void bolt_auth_return_new_error (BoltAuth *auth, GQuark domain, gint code, const char *format, ...) G_GNUC_PRINTF (4, 5); void bolt_auth_return_error (BoltAuth *auth, GError **error); gboolean bolt_auth_check (BoltAuth *auth, GError **error); BoltDevice * bolt_auth_get_device (BoltAuth *auth); BoltSecurity bolt_auth_get_level (BoltAuth *auth); BoltKey * bolt_auth_get_key (BoltAuth *auth); BoltKeyState bolt_auth_get_keystate (BoltAuth *auth); gboolean bolt_auth_has_key (BoltAuth *auth); gpointer bolt_auth_get_origin (BoltAuth *auth); BoltPolicy bolt_auth_get_policy (BoltAuth *auth); void bolt_auth_set_policy (BoltAuth *auth, BoltPolicy policy); BoltStatus bolt_auth_to_status (BoltAuth *auth); BoltAuthFlags bolt_auth_to_flags (BoltAuth *auth, BoltAuthFlags *mask); G_END_DECLS bolt-0.9.2/boltd/bolt-bouncer.c000066400000000000000000000205401417453051000163260ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-bouncer.h" #include "bolt-log.h" #include "bolt-str.h" #include "bolt-exported.h" #include #include static void bouncer_initable_iface_init (GInitableIface *iface); static gboolean bouncer_initialize (GInitable *initable, GCancellable *cancellable, GError **error); #ifndef HAVE_POLKIT_AUTOPTR G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthorizationResult, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitDetails, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitSubject, g_object_unref) #endif struct _BoltBouncer { GObject object; /* */ PolkitAuthority *authority; }; G_DEFINE_TYPE_WITH_CODE (BoltBouncer, bolt_bouncer, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, bouncer_initable_iface_init)); static void bolt_bouncer_finalize (GObject *object) { BoltBouncer *bouncer = BOLT_BOUNCER (object); g_clear_object (&bouncer->authority); G_OBJECT_CLASS (bolt_bouncer_parent_class)->finalize (object); } static void bolt_bouncer_init (BoltBouncer *bouncer) { } static void bolt_bouncer_class_init (BoltBouncerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = bolt_bouncer_finalize; } static void bouncer_initable_iface_init (GInitableIface *iface) { iface->init = bouncer_initialize; } static gboolean bouncer_initialize (GInitable *initable, GCancellable *cancellable, GError **error) { BoltBouncer *bnc = BOLT_BOUNCER (initable); bolt_info (LOG_TOPIC ("bouncer"), "initializing polkit"); bnc->authority = polkit_authority_get_sync (cancellable, error); return bnc->authority != NULL; } /* internal methods */ static gboolean bolt_bouncer_check_action (BoltBouncer *bnc, GDBusMethodInvocation *inv, const char *action, gboolean *authorized, GError **error) { g_autoptr(PolkitSubject) subject = NULL; g_autoptr(PolkitDetails) details = NULL; g_autoptr(PolkitAuthorizationResult) res = NULL; PolkitCheckAuthorizationFlags flags; const char *sender; sender = g_dbus_method_invocation_get_sender (inv); subject = polkit_system_bus_name_new (sender); details = polkit_details_new (); flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; res = polkit_authority_check_authorization_sync (bnc->authority, subject, action, details, flags, NULL, error); if (res == NULL) return FALSE; *authorized = polkit_authorization_result_get_is_authorized (res); return TRUE; } static gboolean handle_authorize_method (BoltExported *exported, GDBusMethodInvocation *inv, GError **error, gpointer user_data) { g_autoptr(PolkitSubject) subject = NULL; g_autoptr(PolkitDetails) details = NULL; gboolean authorized = FALSE; BoltBouncer *bnc; const char *method_name; const char *sender; const char *action; bnc = BOLT_BOUNCER (user_data); method_name = g_dbus_method_invocation_get_method_name (inv); sender = g_dbus_method_invocation_get_sender (inv); subject = polkit_system_bus_name_new (sender); details = polkit_details_new (); action = NULL; if (bolt_streq (method_name, "EnrollDevice")) action = "org.freedesktop.bolt.enroll"; else if (bolt_streq (method_name, "Authorize")) action = "org.freedesktop.bolt.authorize"; else if (bolt_streq (method_name, "ForgetDevice")) action = "org.freedesktop.bolt.manage"; else if (bolt_streq (method_name, "ForcePower")) action = "org.freedesktop.bolt.manage"; else if (bolt_streq (method_name, "ListDomains")) authorized = TRUE; else if (bolt_streq (method_name, "DomainById")) authorized = TRUE; else if (bolt_streq (method_name, "ListDevices")) authorized = TRUE; else if (bolt_streq (method_name, "DeviceByUid")) authorized = TRUE; else if (bolt_streq (method_name, "ListGuards")) authorized = TRUE; if (!authorized && action) { PolkitCheckAuthorizationFlags flags; g_autoptr(PolkitAuthorizationResult) res = NULL; flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; res = polkit_authority_check_authorization_sync (bnc->authority, subject, action, details, flags, NULL, error); if (res == NULL) return FALSE; authorized = polkit_authorization_result_get_is_authorized (res); } if (authorized == FALSE) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Bolt operation '%s' not allowed for user", method_name); return authorized; } static gboolean handle_authorize_property (BoltExported *exported, const char *name, gboolean setting, GDBusMethodInvocation *inv, GError **error, gpointer user_data) { const char *type_name = G_OBJECT_TYPE_NAME (exported); const char *action = NULL; gboolean authorized = FALSE; BoltBouncer *bnc; bnc = BOLT_BOUNCER (user_data); if (bolt_streq (type_name, "BoltDevice")) { if (bolt_streq (name, "label")) action = "org.freedesktop.bolt.manage"; else if (bolt_streq (name, "policy")) action = "org.freedesktop.bolt.manage"; } else if (bolt_streq (type_name, "BoltDomain")) { if (bolt_streq (name, "bootacl")) action = "org.freedesktop.bolt.manage"; } else if (bolt_streq (type_name, "BoltManager")) { if (bolt_streq (name, "auth-mode")) action = "org.freedesktop.bolt.manage"; } if (!authorized && action) { gboolean ok; ok = bolt_bouncer_check_action (bnc, inv, action, &authorized, error); if (!ok) return FALSE; } if (authorized == FALSE) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Setting property of '%s.%s' not allowed for user", type_name, name); return authorized; } /* public methods */ BoltBouncer * bolt_bouncer_new (GCancellable *cancellable, GError **error) { return g_initable_new (BOLT_TYPE_BOUNCER, cancellable, error, NULL); } void bolt_bouncer_add_client (BoltBouncer *bnc, gpointer client) { g_return_if_fail (BOLT_IS_BOUNCER (bnc)); g_return_if_fail (BOLT_IS_EXPORTED (client)); g_signal_connect_object (client, "authorize-method", G_CALLBACK (handle_authorize_method), bnc, 0); g_signal_connect_object (client, "authorize-property", G_CALLBACK (handle_authorize_property), bnc, 0); } bolt-0.9.2/boltd/bolt-bouncer.h000066400000000000000000000022631417453051000163350ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS #define BOLT_TYPE_BOUNCER bolt_bouncer_get_type () G_DECLARE_FINAL_TYPE (BoltBouncer, bolt_bouncer, BOLT, BOUNCER, GObject); BoltBouncer * bolt_bouncer_new (GCancellable *cancellable, GError **error); void bolt_bouncer_add_client (BoltBouncer *bnc, gpointer client); G_END_DECLS bolt-0.9.2/boltd/bolt-config.c000066400000000000000000000074321417453051000161430ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-names.h" #include "bolt-config.h" #define DAEMON_GROUP "config" #define CFG_VERSION 1 #define DEFAULT_POLICY_KEY "DefaultPolicy" #define AUTH_MODE_KEY "AuthMode" const char * bolt_get_store_path (void) { const char *path; /* set by tools or the user directly */ path = g_getenv (BOLT_ENV_DBPATH); if (path != NULL) return path; /* set by systemd >= 240 to an absolute path * taking into account the StateDirectory * unit file setting */ path = g_getenv (BOLT_ENV_STATE_DIRECTORY); if (path != NULL) return path; /* set at compile time (config.h) */ return BOLT_DBDIR; } const char * bolt_get_runtime_directory (void) { const char *path; path = g_getenv (BOLT_ENV_RUNTIME_DIRECTORY); if (path) return path; return "/run/boltd"; } GKeyFile * bolt_config_user_init (void) { GKeyFile *cfg; cfg = g_key_file_new (); g_key_file_set_comment (cfg, NULL, NULL, " Generated by boltd - do not edit", NULL); g_key_file_set_uint64 (cfg, DAEMON_GROUP, "version", CFG_VERSION); return cfg; } BoltTri bolt_config_load_default_policy (GKeyFile *cfg, BoltPolicy *policy, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *str = NULL; BoltPolicy p; g_return_val_if_fail (error == NULL || *error == NULL, TRI_NO); g_return_val_if_fail (policy != NULL, TRI_NO); if (cfg == NULL) return TRI_NO; str = g_key_file_get_string (cfg, DAEMON_GROUP, DEFAULT_POLICY_KEY, &err); if (str == NULL) { int res = bolt_err_notfound (err) ? TRI_NO : TRI_ERROR; if (res == TRI_ERROR) bolt_error_propagate (error, &err); return res; } p = bolt_policy_from_string (str); if (!bolt_policy_validate (p)) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_CFG, "invalid policy: %s", str); return TRI_ERROR; } *policy = p; return TRI_YES; } BoltTri bolt_config_load_auth_mode (GKeyFile *cfg, BoltAuthMode *authmode, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *str = NULL; guint flags = 0; gboolean ok; if (cfg == NULL) return TRI_NO; g_return_val_if_fail (error == NULL || *error == NULL, TRI_NO); str = g_key_file_get_string (cfg, DAEMON_GROUP, AUTH_MODE_KEY, &err); if (str == NULL) { int res = bolt_err_notfound (err) ? TRI_NO : TRI_ERROR; if (res == TRI_ERROR) bolt_error_propagate (error, &err); return res; } ok = bolt_flags_from_string (BOLT_TYPE_AUTH_MODE, str, &flags, error); if (!ok) return TRI_ERROR; if (authmode) *authmode = flags; return TRI_YES; } void bolt_config_set_auth_mode (GKeyFile *cfg, const char *authmode) { g_return_if_fail (cfg != NULL); g_key_file_set_string (cfg, DAEMON_GROUP, AUTH_MODE_KEY, authmode); } bolt-0.9.2/boltd/bolt-config.h000066400000000000000000000031471417453051000161470ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include "bolt-enums.h" G_BEGIN_DECLS /* well known paths */ const char * bolt_get_store_path (void); const char * bolt_get_runtime_directory (void); /* user and system configuration */ typedef enum BoltTri { TRI_ERROR = -1, TRI_NO = 0, TRI_YES = 1, } BoltTri; GKeyFile * bolt_config_user_init (void); BoltTri bolt_config_load_default_policy (GKeyFile *cfg, BoltPolicy *policy, GError **error); BoltTri bolt_config_load_auth_mode (GKeyFile *cfg, BoltAuthMode *authmode, GError **error); void bolt_config_set_auth_mode (GKeyFile *cfg, const char *authmode); G_END_DECLS bolt-0.9.2/boltd/bolt-daemon.c000066400000000000000000000165621417453051000161450ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-dbus.h" #include "bolt-log.h" #include "bolt-manager.h" #include "bolt-names.h" #include "bolt-str.h" #include "bolt-term.h" #include #include #include #include #include #include #include /* globals */ static BoltManager *manager = NULL; static GMainLoop *main_loop = NULL; static guint name_owner_id = 0; static guint sigterm_id = 0; static gboolean handle_sigterm (gpointer user_data) { bolt_debug (LOG_TOPIC ("signal"), "got SIGTERM; shutting down..."); if (g_main_loop_is_running (main_loop)) g_main_loop_quit (main_loop); sigterm_id = 0; return G_SOURCE_REMOVE; } static void install_signal_hanlder (void) { g_autoptr(GSource) source = NULL; source = g_unix_signal_source_new (SIGTERM); if (source == NULL) { bolt_warn (LOG_TOPIC ("signal"), "failed installing SIGTERM handler: %s", g_strerror (errno)); return; } g_source_set_callback (source, handle_sigterm, NULL, NULL); sigterm_id = g_source_attach (source, NULL); bolt_debug (LOG_TOPIC ("signal"), "SIGTERM handler installed [%u]", sigterm_id); } typedef struct _LogCfg { gboolean debug; gboolean journal; char session_id[33]; } LogCfg; static GLogWriterOutput daemon_logger (GLogLevelFlags level, const GLogField *fields, gsize n_fields, gpointer user_data) { g_autoptr(BoltLogCtx) ctx = NULL; GLogWriterOutput res = G_LOG_WRITER_UNHANDLED; LogCfg *log = user_data; g_return_val_if_fail (fields != NULL, G_LOG_WRITER_UNHANDLED); g_return_val_if_fail (n_fields > 0, G_LOG_WRITER_UNHANDLED); ctx = bolt_log_ctx_acquire (fields, n_fields); if (ctx == NULL) return G_LOG_WRITER_UNHANDLED; /* replace the log context field with the session id */ bolt_log_ctx_set_id (ctx, log->session_id); if (level & G_LOG_LEVEL_DEBUG && log->debug == FALSE) { const char *domain = blot_log_ctx_get_domain (ctx); const char *env = g_getenv ("G_MESSAGES_DEBUG"); if (!env || !domain || !strstr (env, domain)) return G_LOG_WRITER_UNHANDLED; } if (fileno (stderr) < 0) return G_LOG_WRITER_UNHANDLED; if (log->journal || g_log_writer_is_journald (fileno (stderr))) res = bolt_log_journal (ctx, level, 0); if (res == G_LOG_WRITER_UNHANDLED) res = bolt_log_stdstream (ctx, level, 0); return res; } static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_autoptr(GError) error = NULL; bolt_debug (LOG_TOPIC ("dbus"), "got the bus [%s]", name); /* */ manager = g_initable_new (BOLT_TYPE_MANAGER, NULL, &error, NULL); if (manager == NULL) { bolt_error (LOG_ERR (error), "could not create manager"); exit (EXIT_FAILURE); } if (!bolt_manager_export (manager, connection, &error)) bolt_warn_err (error, LOG_TOPIC ("dbus"), "error exporting the manager"); } static void on_name_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { bolt_debug (LOG_TOPIC ("dbus"), "got the name"); bolt_manager_got_the_name (manager); } static void on_name_lost (GDBusConnection *connection, const gchar *name, gpointer user_data) { bolt_debug (LOG_TOPIC ("dbus"), "name lost; shutting down..."); if (g_main_loop_is_running (main_loop)) g_main_loop_quit (main_loop); } int main (int argc, char **argv) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GError) error = NULL; gboolean replace = FALSE; gboolean show_version = FALSE; gboolean session_bus = FALSE; GBusType bus_type = G_BUS_TYPE_SYSTEM; GBusNameOwnerFlags flags; LogCfg log = { FALSE, FALSE, }; const GOptionEntry options[] = { { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "Replace old daemon.", NULL }, { "session-bus", 0, 0, G_OPTION_ARG_NONE, &session_bus, "Use the session bus.", NULL}, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &log.debug, "Enable debug output.", NULL }, { "journal", 0, 0, G_OPTION_ARG_NONE, &log.journal, "Force logging to the journal.", NULL}, { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, "Print daemon version.", NULL}, { NULL } }; install_signal_hanlder (); setlocale (LC_ALL, ""); g_setenv ("GIO_USE_VFS", "local", TRUE); g_set_prgname (argv[0]); g_log_set_writer_func (daemon_logger, &log, NULL); context = g_option_context_new (""); g_option_context_set_summary (context, "Thunderbolt system daemon"); g_option_context_add_main_entries (context, options, PACKAGE_NAME); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("%s: %s", g_get_application_name (), error->message); g_printerr ("\n"); g_printerr ("Try \"%s --help\" for more information.", g_get_prgname ()); g_printerr ("\n"); g_option_context_free (context); return EXIT_FAILURE; } if (show_version) { g_print (PACKAGE_NAME " " PACKAGE_VERSION "\n"); return EXIT_SUCCESS; } /* setup logging */ if (log.debug == FALSE && g_getenv ("G_MESSAGES_DEBUG")) { const char *domains = g_getenv ("G_MESSAGES_DEBUG"); log.debug = bolt_streq (domains, "all"); } bolt_log_gen_id (log.session_id); bolt_dbus_ensure_resources (); bolt_msg (LOG_DIRECT (BOLT_LOG_VERSION, PACKAGE_VERSION), LOG_ID (STARTUP), PACKAGE_NAME " " PACKAGE_VERSION " starting up."); bolt_debug ("session id is %s", log.session_id); /* hop on the bus, Gus */ flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; if (replace) flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; if (session_bus) bus_type = G_BUS_TYPE_SESSION; name_owner_id = g_bus_own_name (bus_type, BOLT_DBUS_NAME, flags, on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL); main_loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (main_loop); /* When all is said and done, more is said then done. */ g_main_loop_unref (main_loop); /* we are shutting down */ if (name_owner_id > 0) { g_bus_unown_name (name_owner_id); name_owner_id = 0; } g_clear_object (&manager); bolt_debug ("shutdown complete"); return EXIT_SUCCESS; } bolt-0.9.2/boltd/bolt-device.c000066400000000000000000001303231417453051000161310ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-device.h" #include "bolt-domain.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-glue.h" #include "bolt-io.h" #include "bolt-log.h" #include "bolt-manager.h" #include "bolt-names.h" #include "bolt-store.h" #include "bolt-str.h" #include "bolt-sysfs.h" #include "bolt-time.h" #include #include /* internal methods */ static void device_set_status_internal (BoltDevice *dev, BoltStatus status, gboolean notify); /* dbus property setter */ static gboolean handle_set_label (BoltExported *obj, const char *name, const GValue *value, GError **error); static gboolean handle_set_policy (BoltExported *obj, const char *name, const GValue *value, GError **error); /* dbus method calls */ static GVariant * handle_authorize (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); struct _BoltDevice { BoltExported object; /* device props */ char *uid; char *name; char *vendor; guint gen; BoltDeviceType type; BoltStatus status; /* when device is attached */ BoltAuthFlags aflags; char *syspath; BoltDomain *domain; char *parent; guint64 conntime; guint64 authtime; BoltLinkSpeed linkspeed; /* when device is stored */ BoltStore *store; BoltPolicy policy; BoltKeyState key; guint64 storetime; char *label; }; enum { PROP_0, /* internal properties */ PROP_OBJECT_ID, PROP_STORE, PROP_SECURITY, /* exported properties start here, */ PROP_UID, PROP_NAME, PROP_VENDOR, PROP_GEN, PROP_TYPE, PROP_STATUS, PROP_AUTHFLAGS, PROP_PARENT, PROP_SYSFS, PROP_DOMAIN, PROP_CONNTIME, PROP_AUTHTIME, PROP_LINKSPEED, PROP_STORED, PROP_POLICY, PROP_HAVE_KEY, PROP_STORETIME, PROP_LABEL, PROP_LAST, PROP_EXPORTED = PROP_UID }; static GParamSpec *props[PROP_LAST] = {NULL, }; enum { SIGNAL_STATUS_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE (BoltDevice, bolt_device, BOLT_TYPE_EXPORTED) static void bolt_device_finalize (GObject *object) { BoltDevice *dev = BOLT_DEVICE (object); g_clear_object (&dev->store); g_free (dev->uid); g_free (dev->name); g_free (dev->vendor); g_free (dev->parent); g_free (dev->syspath); g_clear_object (&dev->domain); g_free (dev->label); G_OBJECT_CLASS (bolt_device_parent_class)->finalize (object); } static void bolt_device_init (BoltDevice *dev) { } static void bolt_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltDevice *dev = BOLT_DEVICE (object); switch (prop_id) { case PROP_STORE: g_value_set_object (value, dev->store); break; case PROP_SECURITY: { BoltSecurity sl = BOLT_SECURITY_UNKNOWN; if (dev->domain) sl = bolt_domain_get_security (dev->domain); g_value_set_enum (value, sl); } break; case PROP_OBJECT_ID: case PROP_UID: g_value_set_string (value, dev->uid); break; case PROP_NAME: g_value_set_string (value, dev->name); break; case PROP_TYPE: g_value_set_enum (value, dev->type); break; case PROP_VENDOR: g_value_set_string (value, dev->vendor); break; case PROP_STATUS: g_value_set_enum (value, dev->status); break; case PROP_GEN: g_value_set_uint (value, dev->gen); break; case PROP_AUTHFLAGS: g_value_set_flags (value, dev->aflags); break; case PROP_PARENT: g_value_set_string (value, dev->parent); break; case PROP_SYSFS: g_value_set_string (value, dev->syspath); break; case PROP_DOMAIN: g_value_set_object (value, dev->domain); break; case PROP_CONNTIME: g_value_set_uint64 (value, dev->conntime); break; case PROP_AUTHTIME: g_value_set_uint64 (value, dev->authtime); break; case PROP_LINKSPEED: g_value_set_boxed (value, &dev->linkspeed); break; case PROP_STORED: g_value_set_boolean (value, dev->store != NULL); break; case PROP_POLICY: g_value_set_enum (value, dev->policy); break; case PROP_HAVE_KEY: g_value_set_enum (value, dev->key); break; case PROP_STORETIME: g_value_set_uint64 (value, dev->storetime); break; case PROP_LABEL: g_value_set_string (value, dev->label); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltDevice *dev = BOLT_DEVICE (object); switch (prop_id) { case PROP_STORE: dev->store = g_value_dup_object (value); g_object_notify_by_pspec (object, props[PROP_STORED]); break; case PROP_UID: g_return_if_fail (dev->uid == NULL); dev->uid = g_value_dup_string (value); break; case PROP_NAME: g_clear_pointer (&dev->name, g_free); dev->name = g_value_dup_string (value); break; case PROP_VENDOR: g_clear_pointer (&dev->vendor, g_free); dev->vendor = g_value_dup_string (value); break; case PROP_GEN: dev->gen = g_value_get_uint (value); break; case PROP_TYPE: dev->type = g_value_get_enum (value); break; case PROP_STATUS: { BoltStatus now = g_value_get_enum (value); device_set_status_internal (dev, now, FALSE); break; } case PROP_AUTHFLAGS: dev->aflags = g_value_get_flags (value); break; case PROP_PARENT: g_clear_pointer (&dev->parent, g_free); dev->parent = g_value_dup_string (value); break; case PROP_SYSFS: g_clear_pointer (&dev->syspath, g_free); dev->syspath = g_value_dup_string (value); break; case PROP_DOMAIN: g_clear_object (&dev->domain); dev->domain = g_value_dup_object (value); break; case PROP_CONNTIME: dev->conntime = g_value_get_uint64 (value); break; case PROP_AUTHTIME: dev->authtime = g_value_get_uint64 (value); break; case PROP_LINKSPEED: { BoltLinkSpeed *li = g_value_get_boxed (value); dev->linkspeed = *li; } break; case PROP_POLICY: dev->policy = g_value_get_enum (value); break; case PROP_HAVE_KEY: dev->key = g_value_get_enum (value); break; case PROP_STORETIME: dev->storetime = g_value_get_uint64 (value); break; case PROP_LABEL: g_clear_pointer (&dev->label, g_free); dev->label = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_device_class_init (BoltDeviceClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); BoltExportedClass *exported_class = BOLT_EXPORTED_CLASS (klass); gobject_class->finalize = bolt_device_finalize; gobject_class->get_property = bolt_device_get_property; gobject_class->set_property = bolt_device_set_property; props[PROP_OBJECT_ID] = bolt_param_spec_override (gobject_class, "object-id"); props[PROP_STORE] = g_param_spec_object ("store", NULL, NULL, BOLT_TYPE_STORE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_SECURITY] = g_param_spec_enum ("security", "Security", NULL, BOLT_TYPE_SECURITY, BOLT_SECURITY_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_UID] = g_param_spec_string ("uid", "Uid", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_NAME] = g_param_spec_string ("name", "Name", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_VENDOR] = g_param_spec_string ("vendor", "Vendor", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_GEN] = g_param_spec_uint ("generation", "Generation", NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_TYPE] = g_param_spec_enum ("type", "Type", NULL, BOLT_TYPE_DEVICE_TYPE, BOLT_DEVICE_PERIPHERAL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_STATUS] = g_param_spec_enum ("status", "Status", NULL, BOLT_TYPE_STATUS, BOLT_STATUS_DISCONNECTED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_AUTHFLAGS] = g_param_spec_flags ("authflags", "AuthFlags", NULL, BOLT_TYPE_AUTH_FLAGS, BOLT_AUTH_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_LINKSPEED] = g_param_spec_boxed ("linkspeed", "LinkSpeed", NULL, BOLT_TYPE_LINK_SPEED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_PARENT] = g_param_spec_string ("parent", "Parent", NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_SYSFS] = g_param_spec_string ("sysfs-path", "SysfsPath", NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_DOMAIN] = g_param_spec_object ("domain", "Domain", NULL, BOLT_TYPE_DOMAIN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_CONNTIME] = g_param_spec_uint64 ("conntime", "ConnectTime", NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_AUTHTIME] = g_param_spec_uint64 ("authtime", "AuthorizeTime", NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_STORED] = g_param_spec_boolean ("stored", "Stored", NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_POLICY] = g_param_spec_enum ("policy", "Policy", NULL, BOLT_TYPE_POLICY, BOLT_POLICY_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_HAVE_KEY] = g_param_spec_enum ("key", "Key", NULL, BOLT_TYPE_KEY_STATE, BOLT_KEY_MISSING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_STORETIME] = g_param_spec_uint64 ("storetime", "StoreTime", NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_LABEL] = g_param_spec_string ("label", "Label", NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); signals[SIGNAL_STATUS_CHANGED] = g_signal_new ("status-changed", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, BOLT_TYPE_STATUS); bolt_exported_class_set_interface_info (exported_class, BOLT_DBUS_DEVICE_INTERFACE, BOLT_DBUS_GRESOURCE_PATH); bolt_exported_class_set_object_path (exported_class, BOLT_DBUS_PATH_DEVICES); bolt_exported_class_export_properties (exported_class, PROP_EXPORTED, PROP_LAST, props); bolt_exported_class_property_setter (exported_class, props[PROP_LABEL], handle_set_label); bolt_exported_class_property_setter (exported_class, props[PROP_POLICY], handle_set_policy); bolt_exported_class_export_method (exported_class, "Authorize", handle_authorize); bolt_exported_class_property_wireconv (exported_class, props[PROP_LINKSPEED], "linkspeed-as-variant", bolt_link_speed_to_wire, bolt_link_speed_from_wire); } /* internal methods */ static void device_set_status_internal (BoltDevice *dev, BoltStatus status, gboolean notify) { BoltStatus before; before = dev->status; if (before == status) return; dev->status = status; g_signal_emit (dev, signals[SIGNAL_STATUS_CHANGED], 0, before); if (notify) g_object_notify_by_pspec (G_OBJECT (dev), props[PROP_STATUS]); } static BoltStatus bolt_status_from_info (BoltDevInfo *info) { gint authorized; gboolean have_key; authorized = info->authorized; have_key = info->keysize > 0; if (authorized < 0) return BOLT_STATUS_UNKNOWN; else if (authorized > 0) return BOLT_STATUS_AUTHORIZED; if (have_key) /* authorized == 0 */ return BOLT_STATUS_AUTH_ERROR; return BOLT_STATUS_CONNECTED; } static BoltAuthFlags bolt_auth_flags_from_info (BoltDevInfo *info, BoltDomain *domain, BoltAuthFlags *mask) { BoltSecurity sl; guint val = 0; guint msk = 0; g_return_val_if_fail (info != NULL, 0); g_return_val_if_fail (domain != NULL, 0); msk |= BOLT_AUTH_SECURE; if (info->authorized == 2) val |= BOLT_AUTH_SECURE; sl = bolt_domain_get_security (domain); if (sl == BOLT_SECURITY_SECURE) { msk |= BOLT_AUTH_NOKEY; if (info->keysize < 0) val |= BOLT_AUTH_NOKEY; } msk |= BOLT_AUTH_NOPCIE; if (!bolt_security_allows_pcie (sl)) val |= BOLT_AUTH_NOPCIE; msk |= BOLT_AUTH_BOOT; if (info->boot > 0) val |= BOLT_AUTH_BOOT; if (mask) *mask = msk; return val; } /* device authorization */ static gboolean device_check_parent_auth (BoltDevice *dev, DIR *devdir, int *auth) { g_autoptr(DIR) parent = NULL; g_autoptr(GError) err = NULL; gboolean ok; parent = bolt_opendir_at (dirfd (devdir), "..", O_RDONLY, &err); if (!parent) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("authorize"), "could not open parent directory of device"); return FALSE; } ok = bolt_read_int_at (dirfd (parent), "authorized", auth, &err); if (!ok) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("authorize"), "could not read parent authorization"); return FALSE; } return TRUE; } static void authorize_adjust_error (BoltDevice *dev, DIR *devdir, GError **error) { GError *err; gint auth = -1; gboolean ok; if (error == NULL) return; err = *error; if (bolt_err_inval (err)) { /* EINVAL is reported by the kernel if: * a) device is already authorized * b) parent device is *not* authorized */ /* check for a) */ ok = bolt_read_int_at (dirfd (devdir), "authorized", &auth, NULL); if (ok && auth > 0) { g_clear_error (error); g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_BADSTATE, "device is already authorized"); return; } /* check for b) */ ok = device_check_parent_auth (dev, devdir, &auth); if (ok && auth < 1) { /* parent is not authorized, adjust the error */ g_clear_error (error); g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_AUTHCHAIN, "parent device is not authorized"); return; } } /* if we have a generic, non bolt error, it is most likely a * G_IO_ERROR. We prefix the error message to make it clearer * where the (probably cryptic) error originated */ if (err->domain != BOLT_ERROR) g_prefix_error (error, "%s", "kernel error: "); } typedef struct { BoltAuth *auth; /* the outer callback */ GAsyncReadyCallback callback; gpointer user_data; } AuthData; static void auth_data_free (gpointer data) { AuthData *auth = data; g_clear_object (&auth->auth); g_slice_free (AuthData, auth); } static gboolean authorize_device_internal (BoltDevice *dev, BoltAuth *auth, GError **error) { g_autoptr(DIR) devdir = NULL; BoltKey *key; BoltSecurity level; gboolean ok; key = bolt_auth_get_key (auth); level = bolt_auth_get_level (auth); devdir = bolt_opendir (dev->syspath, error); if (devdir == NULL) return FALSE; ok = bolt_verify_uid (dirfd (devdir), dev->uid, error); if (!ok) return FALSE; if (key) { int keyfd; bolt_debug (LOG_DEV (dev), LOG_TOPIC ("authorize"), "writing key"); keyfd = bolt_openat (dirfd (devdir), "key", O_WRONLY | O_CLOEXEC, 0, error); if (keyfd < 0) return FALSE; ok = bolt_key_write_to (key, keyfd, &level, error); close (keyfd); if (!ok) return FALSE; } bolt_debug (LOG_DEV (dev), LOG_TOPIC ("authorize"), "writing authorization"); ok = bolt_write_char_at (dirfd (devdir), "authorized", level, error); if (!ok) authorize_adjust_error (dev, devdir, error); return ok; } static void authorize_in_thread (GTask *task, gpointer source, gpointer context, GCancellable *cancellable) { GError *error = NULL; BoltDevice *dev = source; AuthData *auth_data = context; BoltAuth *auth = auth_data->auth; gboolean ok; ok = authorize_device_internal (dev, auth, &error); if (!ok) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); } static void authorize_thread_done (GObject *object, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) error = NULL; BoltDevice *dev = BOLT_DEVICE (object); GTask *task = G_TASK (res); BoltStatus status; AuthData *auth_data; BoltAuth *auth; guint aflags; guint mask; gboolean chg; gboolean ok; guint64 now; auth_data = g_task_get_task_data (task); auth = auth_data->auth; ok = g_task_propagate_boolean (task, &error); if (!ok) bolt_auth_return_error (auth, &error); now = bolt_now_in_seconds (); status = bolt_auth_to_status (auth); aflags = bolt_auth_to_flags (auth, &mask); bolt_info (LOG_DEV (dev), LOG_TOPIC ("authorize"), "finished: %s (status: %s, flags: %u)", ok ? "ok" : "FAIL", bolt_status_to_string (status), aflags); g_object_freeze_notify (object); dev->authtime = now; g_object_notify_by_pspec (G_OBJECT (dev), props[PROP_AUTHTIME]); chg = bolt_flags_update (aflags, &dev->aflags, mask); if (chg) g_object_notify_by_pspec (object, props[PROP_AUTHFLAGS]); device_set_status_internal (dev, status, TRUE); g_object_thaw_notify (object); if (dev->store) bolt_store_put_times (dev->store, dev->uid, NULL, "authtime", now, NULL); if (auth_data->callback) auth_data->callback (G_OBJECT (dev), G_ASYNC_RESULT (auth), auth_data->user_data); } static GTask * authorize_prepare (BoltDevice *dev, BoltAuth *auth, GAsyncReadyCallback callback, gpointer user_data) { BoltSecurity lvl; AuthData *auth_data; GTask *task; g_object_set (auth, "device", dev, NULL); if (!bolt_status_is_pending (dev->status)) { bolt_auth_return_new_error (auth, BOLT_ERROR, BOLT_ERROR_BADSTATE, "wrong device state: %s", bolt_status_to_string (dev->status)); if (callback) callback (G_OBJECT (dev), G_ASYNC_RESULT (auth), user_data); return NULL; } task = g_task_new (dev, NULL, authorize_thread_done, NULL); auth_data = g_slice_new (AuthData); auth_data->callback = callback; auth_data->user_data = user_data; auth_data->auth = g_object_ref (auth); g_task_set_task_data (task, auth_data, auth_data_free); g_object_set (dev, "status", BOLT_STATUS_AUTHORIZING, NULL); lvl = bolt_auth_get_level (auth); bolt_info (LOG_DEV (dev), LOG_TOPIC ("authorize"), "authorization prepared for '%s' level", bolt_security_to_string (lvl)); return task; } static gboolean authorize_device_idle (gpointer user_data) { g_autoptr(GTask) task = NULL; g_return_val_if_fail (G_IS_TASK (user_data), G_SOURCE_REMOVE); task = (GTask *) user_data; g_task_run_in_thread (task, authorize_in_thread); return G_SOURCE_REMOVE; } /* dbus property setter */ static gboolean handle_set_label (BoltExported *obj, const char *name, const GValue *value, GError **error) { g_autofree char *nick = NULL; g_autofree char *old = NULL; BoltDevice *dev = BOLT_DEVICE (obj); const char *str = g_value_get_string (value); gboolean ok; nick = bolt_strdup_validate (str); if (nick == NULL) { g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "string is invalid"); return FALSE; } else if (strlen (nick) > 255) { g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "string is too long"); return FALSE; } if (dev->store == NULL) { g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "device is not stored"); return FALSE; } old = dev->label; dev->label = g_steal_pointer (&nick); ok = bolt_store_put_device (dev->store, dev, dev->policy, NULL, error); if (!ok) { bolt_warn_err (*error, LOG_DEV (dev), "failed to store device"); nick = dev->label; dev->label = g_steal_pointer (&old); } return ok; } static gboolean handle_set_policy (BoltExported *obj, const char *name, const GValue *value, GError **error) { BoltDevice *dev = BOLT_DEVICE (obj); BoltPolicy before = dev->policy; BoltPolicy policy = g_value_get_enum (value); gboolean ok; if (policy == BOLT_POLICY_UNKNOWN || policy == BOLT_POLICY_DEFAULT) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "invalid policy (%d)", policy); return FALSE; } else if (dev->store == NULL) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "device is not stored"); return FALSE; } if (policy == dev->policy) return TRUE; ok = bolt_store_put_device (dev->store, dev, policy, NULL, error); if (!ok) { bolt_warn_err (*error, LOG_DEV (dev), "failed to store device"); dev->policy = before; } if (policy == BOLT_POLICY_AUTO) bolt_domain_foreach (dev->domain, bolt_bootacl_add, dev); else if (policy == BOLT_POLICY_MANUAL) bolt_domain_foreach (dev->domain, bolt_bootacl_del, dev); return ok; } /* dbus methods */ static void handle_authorize_done (GObject *device, GAsyncResult *res, gpointer user_data) { BoltDevice *dev = BOLT_DEVICE (device); GDBusMethodInvocation *inv; BoltKeyState ks; GError *error = NULL; BoltAuth *auth; gboolean ok; inv = user_data; auth = BOLT_AUTH (res); ok = bolt_auth_check (auth, &error); if (!ok) { g_dbus_method_invocation_take_error (inv, error); return; } ks = bolt_auth_get_keystate (auth); if (ks == BOLT_KEY_NEW) { g_autoptr(GError) err = NULL; BoltKey *key = bolt_auth_get_key (auth); ok = bolt_store_put_key (dev->store, dev->uid, key, &err); if (!ok) bolt_warn_err (err, "failed to store key"); else g_object_set (dev, "key", ks, NULL); } g_dbus_method_invocation_return_value (inv, g_variant_new ("()")); } static gboolean device_should_upgrade_key (BoltDevice *dev) { gboolean upgrade = FALSE; const char *reason = NULL; if (dev->store == NULL) reason = " (device not stored)"; else if (bolt_flag_isset (dev->aflags, BOLT_AUTH_NOKEY)) reason = " (device cannot use keys)"; else upgrade = TRUE; bolt_msg (LOG_DEV (dev), "performing key upgrade: %s%s", bolt_yesno (upgrade), reason ? : ""); return upgrade; } static GVariant * handle_authorize (BoltExported *object, GVariant *params, GDBusMethodInvocation *inv, GError **error) { g_autoptr(BoltAuth) auth = NULL; g_autoptr(BoltKey) key = NULL; BoltDevice *dev = BOLT_DEVICE (object); BoltSecurity level; /* In bolt_device_authorize the state is also checked, but it * is done already here to fail quicker and avoid accessing the * potentially unset domain (if e.g. the device is not connected). */ if (!bolt_status_is_pending (dev->status)) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_BADSTATE, "wrong device state: %s", bolt_status_to_string (dev->status)); return NULL; } else if (dev->domain == NULL) { bolt_bug (LOG_DEV (dev), "device connected but no domain"); g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_BADSTATE, "device has no domain associated"); return NULL; } level = bolt_domain_get_security (dev->domain); if (level == BOLT_SECURITY_SECURE) { if (dev->key) key = bolt_store_get_key (dev->store, dev->uid, error); else if (device_should_upgrade_key (dev)) key = bolt_key_new (error); else level = BOLT_SECURITY_USER; } /* happens if the key could not be read (fatal error) or if a new * key could not be generated (should practically never happen). * In both cases 'error' will be set. */ if (level == BOLT_SECURITY_SECURE && key == NULL) return NULL; auth = bolt_auth_new (dev, level, key); bolt_device_authorize (dev, auth, handle_authorize_done, inv); return NULL; } /* public methods */ BoltDevice * bolt_device_new_for_udev (struct udev_device *udev, BoltDomain *domain, GError **error) { g_auto(BoltIdent) id = BOLT_IDENT_INIT; const char *uid; BoltDevInfo info; BoltStatus status; BoltAuthFlags aflags; BoltDeviceType type; BoltDevice *dev; gboolean ok; guint64 ct, at; g_return_val_if_fail (udev != NULL, NULL); g_return_val_if_fail (domain != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); uid = bolt_sysfs_device_get_unique_id (udev, error); if (uid == NULL) return NULL; ok = bolt_sysfs_info_for_device (udev, TRUE, &info, error); if (!ok) return NULL; if (info.parent == NULL) type = BOLT_DEVICE_HOST; else type = BOLT_DEVICE_PERIPHERAL; if (type == BOLT_DEVICE_HOST) ok = bolt_sysfs_host_ident (udev, &id, error); else ok = bolt_sysfs_device_ident (udev, &id, error); if (!ok) return NULL; ct = (guint64) info.ctim; status = bolt_status_from_info (&info); aflags = bolt_auth_flags_from_info (&info, domain, NULL); at = bolt_status_is_authorized (status) ? ct : 0; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", id.name, "vendor", id.vendor, "type", type, "generation", info.generation, "status", status, "authflags", aflags, "sysfs-path", info.syspath, "domain", domain, "parent", info.parent, "conntime", ct, "authtime", at, "linkspeed", &info.linkspeed, NULL); return dev; } const char * bolt_device_export (BoltDevice *device, GDBusConnection *connection, GError **error) { gboolean ok; g_return_val_if_fail (BOLT_IS_DEVICE (device), NULL); g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); ok = bolt_exported_export (BOLT_EXPORTED (device), connection, NULL, error); return ok ? bolt_device_get_object_path (device) : NULL; } void bolt_device_unexport (BoltDevice *device) { bolt_exported_unexport (BOLT_EXPORTED (device)); } void bolt_device_authorize (BoltDevice *dev, BoltAuth *auth, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GTask) task = NULL; g_return_if_fail (BOLT_IS_DEVICE (dev)); g_return_if_fail (BOLT_IS_AUTH (auth)); task = authorize_prepare (dev, auth, callback, user_data); if (task == NULL) return; g_task_run_in_thread (task, authorize_in_thread); } void bolt_device_authorize_idle (BoltDevice *dev, BoltAuth *auth, GAsyncReadyCallback callback, gpointer user_data) { GTask *task = NULL; g_return_if_fail (BOLT_IS_DEVICE (dev)); g_return_if_fail (BOLT_IS_AUTH (auth)); task = authorize_prepare (dev, auth, callback, user_data); if (task == NULL) return; g_idle_add (authorize_device_idle, task); } BoltStatus bolt_device_connected (BoltDevice *dev, BoltDomain *domain, struct udev_device *udev) { g_autoptr(GError) err = NULL; BoltAuthFlags aflags; BoltDevInfo info; BoltStatus status; gboolean change; gboolean ok; guint64 ct, at; g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_STATUS_UNKNOWN); g_return_val_if_fail (BOLT_IS_DOMAIN (domain), BOLT_STATUS_UNKNOWN); ok = bolt_sysfs_info_for_device (udev, TRUE, &info, &err); if (!ok) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("udev"), "failed to get device info"); g_clear_error (&err); } status = bolt_status_from_info (&info); aflags = bolt_auth_flags_from_info (&info, domain, NULL); ct = (guint64) info.ctim; at = bolt_status_is_authorized (status) ? ct : 0; change = info.generation != dev->gen; g_object_set (G_OBJECT (dev), "generation", info.generation, "parent", info.parent, "sysfs-path", info.syspath, "domain", domain, "status", status, "authflags", aflags, "conntime", ct, "authtime", at, "linkspeed", &info.linkspeed, NULL); bolt_info (LOG_DEV (dev), "parent is %.13s...", dev->parent); if (change && dev->store) { bolt_info (LOG_DEV (dev), LOG_TOPIC ("store"), "updating device"); ok = bolt_store_put_device (dev->store, dev, BOLT_POLICY_DEFAULT, NULL, &err); if (!ok) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("store"), "failed to update device"); g_clear_error (&err); } } bolt_store_put_times (dev->store, dev->uid, NULL, "conntime", ct, "authtime", at, NULL); return status; } BoltStatus bolt_device_disconnected (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_STATUS_UNKNOWN); g_object_set (G_OBJECT (dev), "parent", NULL, "sysfs-path", NULL, "domain", NULL, "status", BOLT_STATUS_DISCONNECTED, NULL); /* check if we have a new key for the device, and * if so, change its state to KEY_HAVE, because * now it is not new anymore. */ if (dev->key == BOLT_KEY_NEW) g_object_set (G_OBJECT (dev), "key", BOLT_KEY_HAVE, NULL); return dev->status; } gboolean bolt_device_is_connected (BoltDevice *device) { g_return_val_if_fail (BOLT_IS_DEVICE (device), FALSE); return bolt_status_is_connected (device->status); } gboolean bolt_device_is_authorized (BoltDevice *device) { g_return_val_if_fail (BOLT_IS_DEVICE (device), FALSE); return bolt_status_is_authorized (device->status); } BoltStatus bolt_device_update_from_udev (BoltDevice *dev, struct udev_device *udev) { g_autoptr(GError) err = NULL; BoltLinkSpeed linkspeed; BoltAuthFlags aflags; BoltDevInfo info; BoltStatus status; guint mask; gboolean chg; gboolean ok; g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_STATUS_UNKNOWN); g_return_val_if_fail (udev != NULL, BOLT_STATUS_UNKNOWN); /* if we are currently authorizing, let's not update * the status, because we are most likely causing that * udev update and we cannot determine AUTHORIZING from * outside; * The status will be set by authorize_thread_done() */ if (dev->status == BOLT_STATUS_AUTHORIZING) return dev->status; ok = bolt_sysfs_info_for_device (udev, FALSE, &info, &err); if (!ok) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("udev"), "failed to get device info"); return dev->status; } status = bolt_status_from_info (&info); aflags = bolt_auth_flags_from_info (&info, dev->domain, &mask); g_object_freeze_notify (G_OBJECT (dev)); if (bolt_status_is_authorized (status) && !bolt_status_is_authorized (dev->status)) { dev->authtime = bolt_now_in_seconds (); g_object_notify_by_pspec (G_OBJECT (dev), props[PROP_AUTHTIME]); bolt_store_put_times (dev->store, dev->uid, NULL, "authtime", dev->authtime, NULL); } chg = bolt_flags_update (aflags, &dev->aflags, mask); if (chg) g_object_notify_by_pspec (G_OBJECT (dev), props[PROP_AUTHFLAGS]); device_set_status_internal (dev, status, TRUE); bolt_sysfs_read_link_speed (udev, &linkspeed); if (!bolt_link_speed_equal (&dev->linkspeed, &linkspeed)) { dev->linkspeed = linkspeed; g_object_notify_by_pspec (G_OBJECT (dev), props[PROP_LINKSPEED]); } g_object_thaw_notify (G_OBJECT (dev)); return status; } BoltDomain * bolt_device_get_domain (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); return dev->domain; } BoltKeyState bolt_device_get_keystate (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_KEY_MISSING); return dev->key; } const char * bolt_device_get_name (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); return dev->name; } const char * bolt_device_get_object_path (BoltDevice *device) { g_return_val_if_fail (BOLT_IS_DEVICE (device), NULL); if (!bolt_exported_is_exported (BOLT_EXPORTED (device))) return NULL; return bolt_exported_get_object_path (BOLT_EXPORTED (device)); } BoltPolicy bolt_device_get_policy (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_POLICY_UNKNOWN); return dev->policy; } const char * bolt_device_get_uid (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); return dev->uid; } BoltSecurity bolt_device_get_security (BoltDevice *dev) { g_return_val_if_fail (dev != NULL, BOLT_SECURITY_UNKNOWN); g_return_val_if_fail (dev->domain != NULL, BOLT_SECURITY_UNKNOWN); return bolt_domain_get_security (dev->domain); } BoltStatus bolt_device_get_status (BoltDevice *dev) { g_return_val_if_fail (dev != NULL, BOLT_STATUS_UNKNOWN); return dev->status; } BoltAuthFlags bolt_device_get_authflags (BoltDevice *dev) { g_return_val_if_fail (dev != NULL, BOLT_AUTH_NONE); return dev->aflags; } gboolean bolt_device_get_stored (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); return dev->store != NULL; } gboolean bolt_device_has_iommu (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); if (dev->domain == NULL) return FALSE; return bolt_domain_has_iommu (dev->domain); } gboolean bolt_device_has_key (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); return !(dev->key == BOLT_KEY_UNKNOWN || dev->key == BOLT_KEY_MISSING); } const char * bolt_device_get_syspath (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); return dev->syspath; } const char * bolt_device_get_vendor (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); return dev->vendor; } guint bolt_device_get_generation (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); return dev->gen; } BoltDeviceType bolt_device_get_device_type (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_DEVICE_UNKNOWN_TYPE); return dev->type; } gboolean bolt_device_is_host (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_DEVICE_UNKNOWN_TYPE); return dev->type == BOLT_DEVICE_HOST; } const char * bolt_device_get_label (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); return dev->label; } guint64 bolt_device_get_authtime (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); return dev->authtime; } guint64 bolt_device_get_conntime (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); return dev->conntime; } guint64 bolt_device_get_storetime (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); return dev->storetime; } gboolean bolt_device_supports_secure_mode (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); return bolt_flag_isclear (dev->aflags, BOLT_AUTH_NOKEY); } gboolean bolt_device_check_authflag (BoltDevice *dev, BoltAuthFlags flag) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); return bolt_flag_isset (dev->aflags, flag); } gboolean bolt_device_get_key_from_sysfs (BoltDevice *dev, BoltKey **key, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GFile) keyfile = NULL; g_autofree char *keypath = NULL; g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); g_return_val_if_fail (key != NULL && *key == NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (dev->syspath == NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_BADSTATE, "device is not connected"); return FALSE; } keypath = g_build_filename (dev->syspath, "key", NULL); keyfile = g_file_new_for_path (keypath); *key = bolt_key_load_file (keyfile, &err); if (*key != NULL) return TRUE; else if (bolt_err_notfound (err) || bolt_err_nokey (err)) return TRUE; else return bolt_error_propagate (error, &err); } gboolean bolt_device_load_key (BoltDevice *dev, BoltKey **key, GError **error) { BoltKey *k; g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); g_return_val_if_fail (key != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (!bolt_device_has_key (dev) || dev->store == NULL) { *key = NULL; return TRUE; } k = bolt_store_get_key (dev->store, dev->uid, error); if (k == NULL) return FALSE; *key = k; return TRUE; } /* bolt_domain_foreach helpers */ void bolt_bootacl_add (gpointer domain, gpointer device) { g_autoptr(GError) err = NULL; BoltDomain *dom = BOLT_DOMAIN (domain); BoltDevice *dev = BOLT_DEVICE (device); const char *uid = bolt_device_get_uid (dev); gboolean ok; bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (dom), LOG_DEV (dev), "adding %.17s... ", uid); if (!bolt_domain_supports_bootacl (dom)) return; if (bolt_domain_bootacl_contains (dom, uid)) return; ok = bolt_domain_bootacl_add (dom, uid, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (dom), LOG_DEV_UID (uid), "could not add device"); } } void bolt_bootacl_del (gpointer domain, gpointer device) { g_autoptr(GError) err = NULL; BoltDomain *dom = BOLT_DOMAIN (domain); BoltDevice *dev = BOLT_DEVICE (device); const char *uid = bolt_device_get_uid (dev); gboolean ok; bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (dom), LOG_DEV (dev), "removing %.17s...", uid); if (!bolt_domain_supports_bootacl (dom)) return; if (!bolt_domain_bootacl_contains (dom, uid)) return; ok = bolt_domain_bootacl_del (dom, uid, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DEV_UID (uid), LOG_DOM (dom), "could not remove device"); } } bolt-0.9.2/boltd/bolt-device.h000066400000000000000000000113461417453051000161410ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-auth.h" #include "bolt-enums.h" #include "bolt-exported.h" /* forward declaration */ struct udev_device; typedef struct _BoltDomain BoltDomain; G_BEGIN_DECLS #define BOLT_TYPE_DEVICE bolt_device_get_type () G_DECLARE_FINAL_TYPE (BoltDevice, bolt_device, BOLT, DEVICE, BoltExported); BoltDevice * bolt_device_new_for_udev (struct udev_device *udev, BoltDomain *domain, GError **error); const char * bolt_device_export (BoltDevice *device, GDBusConnection *connection, GError **error); void bolt_device_unexport (BoltDevice *device); BoltStatus bolt_device_connected (BoltDevice *dev, BoltDomain *domain, struct udev_device *udev); BoltStatus bolt_device_disconnected (BoltDevice *dev); gboolean bolt_device_is_connected (BoltDevice *device); gboolean bolt_device_is_authorized (BoltDevice *device); BoltStatus bolt_device_update_from_udev (BoltDevice *dev, struct udev_device *udev); void bolt_device_authorize (BoltDevice *dev, BoltAuth *auth, GAsyncReadyCallback callback, gpointer user_data); void bolt_device_authorize_idle (BoltDevice *dev, BoltAuth *auth, GAsyncReadyCallback callback, gpointer user_data); BoltDomain * bolt_device_get_domain (BoltDevice *dev); BoltKeyState bolt_device_get_keystate (BoltDevice *dev); const char * bolt_device_get_name (BoltDevice *dev); const char * bolt_device_get_object_path (BoltDevice *device); BoltPolicy bolt_device_get_policy (BoltDevice *dev); const char * bolt_device_get_uid (BoltDevice *dev); BoltSecurity bolt_device_get_security (BoltDevice *dev); gboolean bolt_device_get_stored (BoltDevice *dev); BoltStatus bolt_device_get_status (BoltDevice *dev); BoltAuthFlags bolt_device_get_authflags (BoltDevice *dev); const char * bolt_device_get_syspath (BoltDevice *dev); const char * bolt_device_get_vendor (BoltDevice *dev); BoltDeviceType bolt_device_get_device_type (BoltDevice *dev); gboolean bolt_device_is_host (BoltDevice *dev); const char * bolt_device_get_label (BoltDevice *dev); guint64 bolt_device_get_authtime (BoltDevice *dev); guint64 bolt_device_get_conntime (BoltDevice *dev); guint64 bolt_device_get_storetime (BoltDevice *dev); guint bolt_device_get_generation (BoltDevice *dev); gboolean bolt_device_has_iommu (BoltDevice *dev); gboolean bolt_device_has_key (BoltDevice *dev); gboolean bolt_device_supports_secure_mode (BoltDevice *dev); gboolean bolt_device_check_authflag (BoltDevice *dev, BoltAuthFlags flag); gboolean bolt_device_get_key_from_sysfs (BoltDevice *dev, BoltKey **key, GError **error); gboolean bolt_device_load_key (BoltDevice *dev, BoltKey **key, GError **error); /* bolt_domain_foreach helpers */ void bolt_bootacl_add (gpointer domain, gpointer device); void bolt_bootacl_del (gpointer domain, gpointer device); G_END_DECLS bolt-0.9.2/boltd/bolt-domain.c000066400000000000000000000777021417453051000161540ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-error.h" #include "bolt-glue.h" #include "bolt-log.h" #include "bolt-str.h" #include "bolt-store.h" #include "bolt-sysfs.h" #include "bolt-list.h" #include "bolt-domain.h" #include #include static void bolt_domain_store_setter (BoltDomain *domain, const GValue *val); static void bolt_domain_bootacl_open_log (BoltDomain *domain); static void bolt_domain_bootacl_remove_log (BoltDomain *domain); /* dbus property setter */ static gboolean handle_set_bootacl (BoltExported *obj, const char *name, const GValue *value, GError **error); struct _BoltDomain { BoltExported object; /* internal list */ BoltList domains; gint sort; BoltStore *store; BoltJournal *acllog; /* persistent */ char *uid; /* sysfs */ char *id; char *syspath; BoltSecurity security; GStrv bootacl; gboolean iommu; }; enum { PROP_0, /* internal properties */ PROP_STORE, PROP_OBJECT_ID, /* exported properties */ PROP_UID, PROP_ID, PROP_SYSPATH, PROP_SECURITY, PROP_BOOTACL, PROP_IOMMU, PROP_LAST, PROP_EXPORTED = PROP_UID }; static GParamSpec *props[PROP_LAST] = { NULL, }; enum { SIGNAL_BOOTACL_CHANGED, SIGNAL_BOOTACL_ALLOC, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0, }; G_DEFINE_TYPE (BoltDomain, bolt_domain, BOLT_TYPE_EXPORTED) static void bolt_domain_finalize (GObject *object) { BoltDomain *dom = BOLT_DOMAIN (object); g_clear_object (&dom->store); g_clear_object (&dom->acllog); g_free (dom->uid); g_free (dom->id); g_free (dom->syspath); g_strfreev (dom->bootacl); G_OBJECT_CLASS (bolt_domain_parent_class)->finalize (object); } static void bolt_domain_init (BoltDomain *dom) { bolt_list_init (&dom->domains); } static void bolt_domain_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltDomain *dom = BOLT_DOMAIN (object); switch (prop_id) { case PROP_STORE: g_value_set_object (value, dom->store); break; case PROP_OBJECT_ID: case PROP_UID: g_value_set_string (value, dom->uid); break; case PROP_ID: g_value_set_string (value, dom->id); break; case PROP_SYSPATH: g_value_set_string (value, dom->syspath); break; case PROP_SECURITY: g_value_set_enum (value, dom->security); break; case PROP_BOOTACL: g_value_set_boxed (value, dom->bootacl); break; case PROP_IOMMU: g_value_set_boolean (value, dom->iommu); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_domain_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltDomain *dom = BOLT_DOMAIN (object); switch (prop_id) { case PROP_STORE: bolt_domain_store_setter (dom, value); break; case PROP_UID: dom->uid = g_value_dup_string (value); break; case PROP_ID: dom->id = g_value_dup_string (value); break; case PROP_SYSPATH: g_clear_pointer (&dom->syspath, g_free); dom->syspath = g_value_dup_string (value); break; case PROP_SECURITY: dom->security = g_value_get_enum (value); break; case PROP_BOOTACL: g_strfreev (dom->bootacl); dom->bootacl = g_value_dup_boxed (value); break; case PROP_IOMMU: dom->iommu = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_domain_class_init (BoltDomainClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); BoltExportedClass *exported_class = BOLT_EXPORTED_CLASS (klass); gobject_class->finalize = bolt_domain_finalize; gobject_class->get_property = bolt_domain_get_property; gobject_class->set_property = bolt_domain_set_property; props[PROP_STORE] = g_param_spec_object ("store", NULL, NULL, BOLT_TYPE_STORE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_OBJECT_ID] = bolt_param_spec_override (gobject_class, "object-id"); props[PROP_UID] = g_param_spec_string ("uid", "Uid", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_ID] = g_param_spec_string ("id", "Id", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_SYSPATH] = g_param_spec_string ("syspath", "SysfsPath", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_SECURITY] = g_param_spec_enum ("security", "SecurityLevel", NULL, BOLT_TYPE_SECURITY, BOLT_SECURITY_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_BOOTACL] = g_param_spec_boxed ("bootacl", "BootACL", NULL, G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_IOMMU] = g_param_spec_boolean ("iommu", "IOMMU", NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); bolt_exported_class_set_interface_info (exported_class, BOLT_DBUS_DOMAIN_INTERFACE, BOLT_DBUS_GRESOURCE_PATH); bolt_exported_class_set_object_path (exported_class, BOLT_DBUS_PATH_DOMAINS); bolt_exported_class_export_properties (exported_class, PROP_EXPORTED, PROP_LAST, props); bolt_exported_class_property_setter (exported_class, props[PROP_BOOTACL], handle_set_bootacl); signals[SIGNAL_BOOTACL_CHANGED] = g_signal_new ("bootacl-changed", BOLT_TYPE_DOMAIN, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_BOOLEAN, G_TYPE_HASH_TABLE); signals[SIGNAL_BOOTACL_ALLOC] = g_signal_new ("bootacl-alloc", BOLT_TYPE_DOMAIN, G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_true_handled, NULL, NULL, G_TYPE_BOOLEAN, 3, G_TYPE_STRV, G_TYPE_STRING, G_TYPE_POINTER); } /* */ static void bolt_domain_store_setter (BoltDomain *domain, const GValue *value) { BoltStore *store; store = g_value_get_object (value); if (domain->store == store) return; if (domain->store) { bolt_domain_bootacl_remove_log (domain); g_clear_object (&domain->store); } if (store != NULL) { domain->store = g_object_ref (store); bolt_domain_bootacl_open_log (domain); } } static void bolt_domain_bootacl_open_log (BoltDomain *domain) { g_autoptr(GError) err = NULL; BoltJournal *log = NULL; const char *uid = domain->uid; log = bolt_store_open_journal (domain->store, "bootacl", uid, &err); if (log == NULL) bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), "could not open journal"); domain->acllog = log; } static void bolt_domain_bootacl_remove_log (BoltDomain *domain) { g_autoptr(GError) err = NULL; gboolean ok; if (domain->acllog == NULL) return; g_clear_object (&domain->acllog); g_return_if_fail (domain->store != NULL); bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "removing journal"); ok = bolt_store_del_journal (domain->store, "bootacl", domain->uid, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), "could not remove journal"); } static gboolean bolt_domain_bootacl_can_update (BoltDomain *domain, GError **error) { if (!bolt_domain_supports_bootacl (domain)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "boot ACL not supported on domain '%s'", domain->uid); return FALSE; } if (domain->syspath == NULL && domain->acllog == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "domain offline and no bootacl journal"); return FALSE; } return TRUE; } static void bolt_domain_bootacl_update (BoltDomain *domain, GStrv *acl, GHashTable *diff_hint) { g_autoptr(GHashTable) diff = NULL; gboolean pending; gboolean same; guint signal; g_return_if_fail (domain != NULL); g_return_if_fail (acl != NULL); same = bolt_strv_equal (domain->bootacl, *acl); if (same) { bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "acl unchanged, not updating"); return; } bolt_swap (domain->bootacl, *acl); g_object_notify_by_pspec (G_OBJECT (domain), props[PROP_BOOTACL]); if (domain->store) { g_autoptr(GError) err = NULL; gboolean ok; ok = bolt_store_put_domain (domain->store, domain, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), "could not update domain"); } signal = signals[SIGNAL_BOOTACL_CHANGED]; pending = g_signal_has_handler_pending (domain, signal, 0, FALSE); if (!pending) return; if (diff_hint != NULL) diff = g_hash_table_ref (diff_hint); else diff = bolt_strv_diff (*acl, domain->bootacl); g_signal_emit (domain, signals[SIGNAL_BOOTACL_CHANGED], 0, g_hash_table_size (diff) > 0, diff); } static gboolean bolt_domain_bootacl_remove (BoltDomain *domain, GStrv acl, const char *uuid, GError **error) { char **target = NULL; g_return_val_if_fail (acl != NULL, FALSE); target = bolt_strv_contains (acl, uuid); if (target == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "device '%s' not in boot ACL of domain '%s'", uuid, domain->id); return FALSE; } bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "removing '%s' from bootacl", uuid); bolt_set_strdup (target, ""); return TRUE; } static void bolt_domain_bootacl_sync (BoltDomain *domain, GStrv *sysacl) { g_autoptr(GError) err = NULL; g_autoptr(GPtrArray) diff = NULL; g_auto(GStrv) acl = NULL; BoltJournal *log = domain->acllog; gboolean ok; if (bolt_strv_isempty (sysacl) || log == NULL) return; bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "synchronizing journal"); acl = g_strdupv (*sysacl); diff = bolt_journal_list (log, &err); if (diff == NULL) { bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), "could not list bootacl changes"); return; } bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "journal contains %u entries", diff->len); for (guint i = 0; i < diff->len; i++) { BoltJournalItem *item = g_ptr_array_index (diff, i); BoltJournalOp op = item->op; const char *uid = item->id; ok = TRUE; bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "applying op '%c' for '%s'", op, uid); switch (op) { case BOLT_JOURNAL_ADDED: if (bolt_strv_contains (acl, uid)) { bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "'%s' already in acl", uid); continue; } bolt_domain_bootacl_allocate (domain, acl, uid); break; case BOLT_JOURNAL_REMOVED: if (!bolt_strv_contains (acl, uid)) { bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "'%s' already removed from acl", uid); continue; } ok = bolt_domain_bootacl_remove (domain, acl, uid, &err); break; default: bolt_bug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "handled journal op %d", item->op); break; } if (!ok) { bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), LOG_DEV_UID (uid), "applying journal op (%d) failed for %.17s", item->op, uid); g_clear_error (&err); } } ok = bolt_journal_reset (log, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), "could not reset journal"); g_clear_error (&err); /* keep going */ } ok = bolt_sysfs_write_boot_acl (domain->syspath, acl, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), "could not write changed bootacl to sysfs"); return; } /* all good, we replace the passed in one with our version */ bolt_swap (acl, *sysacl); } /* */ BoltDomain * bolt_domain_new_for_udev (struct udev_device *udev, const char *uid, GError **error) { g_autoptr(GError) err = NULL; g_auto(GStrv) acl = NULL; BoltDomain *dom = NULL; BoltSecurity security = BOLT_SECURITY_UNKNOWN; const char *syspath; const char *sysname; gboolean iommu; gboolean ok; gint sort = -1; g_return_val_if_fail (udev != NULL, NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (!bolt_sysfs_device_is_domain (udev, error)) return NULL; syspath = udev_device_get_syspath (udev); sysname = udev_device_get_sysname (udev); if (sysname == NULL) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "could not get domain id from udev"); return NULL; } if (g_str_has_prefix (sysname, "domain")) { const char *ptr = sysname + strlen ("domain"); bolt_str_parse_as_int (ptr, &sort, NULL); } security = bolt_sysfs_security_for_device (udev, error); if (security == BOLT_SECURITY_UNKNOWN) return NULL; ok = bolt_sysfs_read_boot_acl (udev, &acl, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("udev"), "failed to read boot_acl"); g_clear_error (&err); } ok = bolt_sysfs_read_iommu (udev, &iommu, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("udev"), "failed to read iommu"); g_clear_error (&err); } dom = g_object_new (BOLT_TYPE_DOMAIN, "uid", uid, "id", sysname, "syspath", syspath, "security", security, "bootacl", acl, "iommu", iommu, NULL); return dom; } const char * bolt_domain_get_uid (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); return domain->uid; } const char * bolt_domain_get_id (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); return domain->id; } const char * bolt_domain_get_syspath (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); return domain->syspath; } BoltSecurity bolt_domain_get_security (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), BOLT_SECURITY_UNKNOWN); return domain->security; } GStrv bolt_domain_get_bootacl (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); return domain->bootacl; } GStrv bolt_domain_dup_bootacl (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); return g_strdupv (domain->bootacl); } gboolean bolt_domain_is_stored (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); return domain->store != NULL; } gboolean bolt_domain_is_connected (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); return domain->syspath != NULL; } gboolean bolt_domain_has_iommu (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); return domain->iommu; } void bolt_domain_export (BoltDomain *domain, GDBusConnection *bus) { g_autoptr(GError) err = NULL; BoltExported *exported; const char *opath; gboolean ok; g_return_if_fail (BOLT_IS_DOMAIN (domain)); g_return_if_fail (G_IS_DBUS_CONNECTION (bus)); exported = BOLT_EXPORTED (domain); ok = bolt_exported_export (exported, bus, NULL, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("dbus"), "error exporting a domain"); return; } opath = bolt_exported_get_object_path (exported); bolt_info (LOG_TOPIC ("dbus"), LOG_DOM (domain), "exported domain at %s", opath); } void bolt_domain_connected (BoltDomain *domain, struct udev_device *dev) { g_autoptr(GError) err = NULL; g_auto(GStrv) acl = NULL; BoltSecurity security; const char *syspath; const char *id; gboolean iommu; gboolean ok; g_return_if_fail (BOLT_IS_DOMAIN (domain)); g_return_if_fail (dev != NULL); id = udev_device_get_sysname (dev); syspath = udev_device_get_syspath (dev); if (domain->syspath != NULL && !bolt_streq (domain->syspath, syspath)) { bolt_warn (LOG_TOPIC ("domain"), LOG_DOM (domain), "already connected domain at '%s'" "reconnected at '%s'", domain->syspath, syspath); g_free (domain->syspath); g_free (domain->id); } security = bolt_sysfs_security_for_device (dev, &err); if (security == BOLT_SECURITY_UNKNOWN) { bolt_warn_err (err, LOG_TOPIC ("udev"), "error getting security from sysfs"); g_clear_error (&err); } ok = bolt_sysfs_read_iommu (dev, &iommu, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("udev"), "failed to read iommu"); g_clear_error (&err); } g_object_freeze_notify (G_OBJECT (domain)); domain->id = g_strdup (id); domain->syspath = g_strdup (syspath); domain->security = security; domain->iommu = iommu; g_object_notify_by_pspec (G_OBJECT (domain), props[PROP_ID]); g_object_notify_by_pspec (G_OBJECT (domain), props[PROP_SYSPATH]); g_object_notify_by_pspec (G_OBJECT (domain), props[PROP_SECURITY]); g_object_notify_by_pspec (G_OBJECT (domain), props[PROP_IOMMU]); ok = bolt_sysfs_read_boot_acl (dev, &acl, &err); if (!ok) bolt_warn_err (err, "failed to get boot_acl"); bolt_domain_bootacl_sync (domain, &acl); bolt_domain_bootacl_update (domain, &acl, NULL); g_object_thaw_notify (G_OBJECT (domain)); bolt_msg (LOG_DOM (domain), "connected: as %s [%s] (%s)", id, bolt_security_to_string (security), syspath); } void bolt_domain_disconnected (BoltDomain *domain) { g_return_if_fail (BOLT_IS_DOMAIN (domain)); bolt_msg (LOG_DOM (domain), "disconnected from %s", domain->syspath); g_object_freeze_notify (G_OBJECT (domain)); g_clear_pointer (&domain->id, g_free); g_clear_pointer (&domain->syspath, g_free); g_object_notify_by_pspec (G_OBJECT (domain), props[PROP_ID]); g_object_notify_by_pspec (G_OBJECT (domain), props[PROP_SYSPATH]); g_object_thaw_notify (G_OBJECT (domain)); } void bolt_domain_update_from_udev (BoltDomain *domain, struct udev_device *udev) { g_autoptr(GError) err = NULL; g_auto(GStrv) acl = NULL; gboolean ok; g_return_if_fail (BOLT_IS_DOMAIN (domain)); g_return_if_fail (udev != NULL); ok = bolt_sysfs_read_boot_acl (udev, &acl, &err); if (!ok) { bolt_warn_err (err, "failed to get boot_acl"); return; } bolt_domain_bootacl_update (domain, &acl, NULL); } gboolean bolt_domain_can_delete (BoltDomain *domain, GError **error) { BoltJournal *log; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); log = domain->acllog; if (log && !bolt_journal_is_fresh (log)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY, "boot acl journal is not empty"); return FALSE; } return TRUE; } gboolean bolt_domain_supports_bootacl (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); return !bolt_strv_isempty (domain->bootacl); } guint bolt_domain_bootacl_slots (BoltDomain *domain, guint *n_free) { guint slots = 0; guint unused = 0; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), 0); if (bolt_strv_isempty (domain->bootacl)) { if (n_free) *n_free = 0; return 0; } for (char **iter = domain->bootacl; *iter; iter++) { slots++; if (bolt_strzero (*iter)) unused++; } if (n_free) *n_free = unused; return slots; } gboolean bolt_domain_bootacl_contains (BoltDomain *domain, const char *uuid) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); g_return_val_if_fail (uuid != NULL, FALSE); return domain->bootacl != NULL && g_strv_contains ((char const * const *) domain->bootacl, uuid); } /* domain list management */ BoltDomain * bolt_domain_insert (BoltDomain *list, BoltDomain *domain) { BoltList iter; BoltList *n; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), list); /* the list as a whole takes one reference */ g_object_ref (domain); if (list == NULL) return domain; bolt_nhlist_iter_init (&iter, &list->domains); while ((n = bolt_nhlist_iter_next (&iter))) { BoltDomain *d = bolt_list_entry (n, BoltDomain, domains); if (domain->sort > d->sort) break; } /* all existing domains are sorted before, * so add to the end of the list */ if (n == NULL) n = list->domains.prev; bolt_list_add_after (n, &domain->domains); return list; } BoltDomain * bolt_domain_remove (BoltDomain *list, BoltDomain *domain) { BoltList *head; g_return_val_if_fail (BOLT_IS_DOMAIN (list), NULL); g_return_val_if_fail (BOLT_IS_DOMAIN (domain), list); head = bolt_nhlist_del (&list->domains, &domain->domains); /* the list as a whole has one reference, release it */ g_object_unref (domain); if (head == NULL) return NULL; return bolt_list_entry (head, BoltDomain, domains); } const char ** bolt_domain_bootacl_get_used (BoltDomain *domain, guint *n_used) { GPtrArray *res; guint used = 0; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); res = g_ptr_array_new (); for (char **iter = domain->bootacl; iter && *iter; iter++) { if (strlen (*iter)) { g_ptr_array_add (res, *iter); used++; } } g_ptr_array_add (res, NULL); if (n_used != NULL) *n_used = used; return (const char **) g_ptr_array_free (res, FALSE); } void bolt_domain_bootacl_allocate (BoltDomain *domain, GStrv acl, const char *uuid) { char **target; gboolean ok; gint slot = -1; g_return_if_fail (BOLT_IS_DOMAIN (domain)); g_return_if_fail (acl != NULL && *acl != NULL); g_return_if_fail (uuid != NULL); /* find the first empty slot, if there is any */ target = bolt_strv_contains (acl, ""); if (target) slot = target - acl; bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "slot before allocation: %d", slot); g_signal_emit (domain, signals[SIGNAL_BOOTACL_ALLOC], 0, acl, uuid, &slot, &ok); bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "slot after allocation: %d [handled: %s]", slot, bolt_yesno (ok)); if (slot == -1) { /* no slot was allocated so far, lets do FIFO */ target = bolt_strv_rotate_left (acl); slot = target - acl; } bolt_debug (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "adding '%s' as bootacl[%d] (was '%s')", uuid, slot, acl[slot]); bolt_set_strdup (&acl[slot], uuid); } gboolean bolt_domain_bootacl_set (BoltDomain *domain, GStrv acl, GError **error) { g_autoptr(GHashTable) diff = NULL; g_auto(GStrv) tmp = NULL; BoltJournal *log; gboolean online; gboolean same; gboolean ok; guint ours, theirs; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = bolt_domain_bootacl_can_update (domain, error); if (!ok) return FALSE; online = domain->syspath != NULL; log = domain->acllog; theirs = bolt_gstrv_length0 (acl); ours = g_strv_length (domain->bootacl); if (ours != theirs) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "boot ACL length mismatch (ours: %u yours: %u)", ours, theirs); return FALSE; } same = bolt_strv_equal (acl, domain->bootacl); /* NB: we return FALSE but set no error */ if (same == TRUE) return FALSE; diff = bolt_strv_diff (domain->bootacl, acl); if (online) ok = bolt_sysfs_write_boot_acl (domain->syspath, acl, error); else ok = bolt_journal_put_diff (log, diff, error); if (!ok) return FALSE; tmp = g_strdupv (acl); bolt_domain_bootacl_update (domain, &tmp, diff); return TRUE; } gboolean bolt_domain_bootacl_add (BoltDomain *domain, const char *uuid, GError **error) { g_auto(GStrv) acl = NULL; BoltJournal *log = NULL; gboolean online; gboolean ok; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); g_return_val_if_fail (uuid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = bolt_domain_bootacl_can_update (domain, error); if (!ok) return FALSE; if (bolt_domain_bootacl_contains (domain, uuid)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS, "'%s' already in boot ACL of domain '%s'", uuid, domain->id); return FALSE; } online = domain->syspath != NULL; log = domain->acllog; acl = g_strdupv (domain->bootacl); bolt_domain_bootacl_allocate (domain, acl, uuid); if (online) ok = bolt_sysfs_write_boot_acl (domain->syspath, acl, error); else ok = bolt_journal_put (log, uuid, BOLT_JOURNAL_ADDED, error); if (!ok) return FALSE; bolt_domain_bootacl_update (domain, &acl, NULL); return TRUE; } gboolean bolt_domain_bootacl_del (BoltDomain *domain, const char *uuid, GError **error) { g_autoptr(GHashTable) diff = NULL; g_auto(GStrv) acl = NULL; BoltJournal *log; gboolean ok; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); g_return_val_if_fail (uuid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = bolt_domain_bootacl_can_update (domain, error); if (!ok) return FALSE; log = domain->acllog; acl = g_strdupv (domain->bootacl); ok = bolt_domain_bootacl_remove (domain, acl, uuid, error); if (!ok) return FALSE; if (domain->syspath != NULL) ok = bolt_sysfs_write_boot_acl (domain->syspath, acl, error); else ok = bolt_journal_put (log, uuid, BOLT_JOURNAL_REMOVED, error); if (!ok) return FALSE; diff = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (diff, (gpointer) uuid, GINT_TO_POINTER ('-')); bolt_domain_bootacl_update (domain, &acl, diff); return TRUE; } BoltDomain * bolt_domain_next (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); return bolt_list_entry (domain->domains.next, BoltDomain, domains); } BoltDomain * bolt_domain_prev (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); return bolt_list_entry (domain->domains.prev, BoltDomain, domains); } guint bolt_domain_count (BoltDomain *domain) { if (domain == NULL) return 0; return bolt_nhlist_len (&domain->domains); } void bolt_domain_foreach (BoltDomain *list, GFunc func, gpointer data) { BoltList iter; BoltList *n; if (list == NULL) return; bolt_nhlist_iter_init (&iter, &list->domains); while ((n = bolt_nhlist_iter_next (&iter))) { BoltDomain *d = bolt_list_entry (n, BoltDomain, domains); func ((gpointer) d, data); } } BoltDomain * bolt_domain_find_id (BoltDomain *list, const char *id, GError **error) { BoltList iter; BoltList *n; g_return_val_if_fail (id != NULL, NULL); if (list == NULL) goto notfound; bolt_nhlist_iter_init (&iter, &list->domains); while ((n = bolt_nhlist_iter_next (&iter))) { BoltDomain *d = bolt_list_entry (n, BoltDomain, domains); if (bolt_streq (d->id, id) || bolt_streq (d->uid, id)) return d; } notfound: g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "domain with id '%s' could not be found.", id); return NULL; } void bolt_domain_clear (BoltDomain **list) { BoltDomain *iter; g_return_if_fail (list != NULL); iter = *list; while (bolt_domain_count (iter)) iter = bolt_domain_remove (iter, iter); *list = iter; } /* dbus property setter */ static gboolean handle_set_bootacl (BoltExported *obj, const char *name, const GValue *value, GError **error) { BoltDomain *domain = BOLT_DOMAIN (obj); GStrv acl; gboolean ok; acl = (GStrv) g_value_get_boxed (value); if (!bolt_uuidv_check (acl, TRUE, error)) return FALSE; /* does check if we can actually update the boot-acl, * i.e. calls bolt_domain_bootacl_can_update */ ok = bolt_domain_bootacl_set (domain, acl, error); // maybe adjust the G_IO_ERROR to a G_DBUS_ERROR ? return ok; } bolt-0.9.2/boltd/bolt-domain.h000066400000000000000000000110661417453051000161500ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-exported.h" #include G_BEGIN_DECLS /* forward declaration */ struct udev_device; #define BOLT_TYPE_DOMAIN bolt_domain_get_type () G_DECLARE_FINAL_TYPE (BoltDomain, bolt_domain, BOLT, DOMAIN, BoltExported); BoltDomain * bolt_domain_new_for_udev (struct udev_device *udev, const char *uid, GError **error) G_GNUC_WARN_UNUSED_RESULT; const char * bolt_domain_get_uid (BoltDomain *domain); const char * bolt_domain_get_id (BoltDomain *domain); const char * bolt_domain_get_syspath (BoltDomain *domain); BoltSecurity bolt_domain_get_security (BoltDomain *domain); GStrv bolt_domain_get_bootacl (BoltDomain *domain); GStrv bolt_domain_dup_bootacl (BoltDomain *domain); gboolean bolt_domain_is_stored (BoltDomain *domain); gboolean bolt_domain_is_connected (BoltDomain *domain); gboolean bolt_domain_has_iommu (BoltDomain *domain); void bolt_domain_export (BoltDomain *domain, GDBusConnection *connection); void bolt_domain_connected (BoltDomain *domain, struct udev_device *udev); void bolt_domain_disconnected (BoltDomain *domain); void bolt_domain_update_from_udev (BoltDomain *domain, struct udev_device *udev); gboolean bolt_domain_can_delete (BoltDomain *domain, GError **error); /* boot acl related functions */ gboolean bolt_domain_supports_bootacl (BoltDomain *domain); guint bolt_domain_bootacl_slots (BoltDomain *domain, guint *n_free); gboolean bolt_domain_bootacl_contains (BoltDomain *domain, const char *uuid); const char ** bolt_domain_bootacl_get_used (BoltDomain *domain, guint *n_used); void bolt_domain_bootacl_allocate (BoltDomain *domain, GStrv acl, const char *uuid); gboolean bolt_domain_bootacl_set (BoltDomain *domain, GStrv acl, GError **error); gboolean bolt_domain_bootacl_add (BoltDomain *domain, const char *uuid, GError **error); gboolean bolt_domain_bootacl_del (BoltDomain *domain, const char *uuid, GError **error); /* domain list management */ BoltDomain * bolt_domain_insert (BoltDomain *list, BoltDomain *domain) G_GNUC_WARN_UNUSED_RESULT; BoltDomain * bolt_domain_remove (BoltDomain *list, BoltDomain *domain) G_GNUC_WARN_UNUSED_RESULT; BoltDomain * bolt_domain_next (BoltDomain *domain); BoltDomain * bolt_domain_prev (BoltDomain *domain); guint bolt_domain_count (BoltDomain *domain); void bolt_domain_foreach (BoltDomain *list, GFunc func, gpointer user_data); BoltDomain * bolt_domain_find_id (BoltDomain *list, const char *id, GError **error); void bolt_domain_clear (BoltDomain **list); G_END_DECLS bolt-0.9.2/boltd/bolt-exported.c000066400000000000000000001034311417453051000165240ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-dbus.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-glue.h" #include "bolt-log.h" #include "bolt-names.h" #include "bolt-str.h" #include "bolt-exported.h" typedef struct _BoltExportedMethod BoltExportedMethod; typedef struct _BoltExportedProp BoltExportedProp; static GVariant * bolt_exported_get_prop (BoltExported *exported, BoltExportedProp *prop); static char * bolt_exported_make_object_path (BoltExported *exported); static void bolt_exported_dispatch_properties_changed (GObject *object, guint n_pspecs, GParamSpec **pspecs); static gboolean handle_authorize_method_default (BoltExported *exported, GDBusMethodInvocation *inv, GError **error); static gboolean handle_authorize_property_default (BoltExported *exported, const char *name, gboolean setting, GDBusMethodInvocation *invocation, GError **error); static void bolt_exported_method_free (gpointer data); static void bolt_exported_prop_free (gpointer data); struct _BoltExportedMethod { char *name; BoltExportedMethodHandler handler; }; struct _BoltExportedProp { GParamSpec *spec; const char *name_obj; /* shortcut for spec->name */ const char *name_bus; GVariantType *signature; /* optional */ BoltExportedSetter setter; /* auto string conversion */ BoltWireConv *conv; }; struct _BoltExportedClassPrivate { char *iface_name; GDBusInterfaceInfo *iface_info; char *object_path; GHashTable *methods; GHashTable *properties; }; typedef struct _BoltExportedPrivate { GDBusConnection *dbus; char *object_path; /* if exported */ guint registration; /* property changes */ GPtrArray *props_changed; guint props_changed_id; } BoltExportedPrivate; static gpointer bolt_exported_parent_class = NULL; static gint BoltExported_private_offset = 0; static void bolt_exported_init (GTypeInstance *, gpointer g_class); static void bolt_exported_class_init (BoltExportedClass *klass); static void bolt_exported_base_init (gpointer g_class); static void bolt_exported_base_finalize (gpointer g_class); #define GET_PRIV(self) G_STRUCT_MEMBER_P (self, BoltExported_private_offset) #define CHAIN_UP(method) G_OBJECT_CLASS (bolt_exported_parent_class)->method GType bolt_exported_get_type (void) { static volatile gsize exported_type = 0; if (g_once_init_enter (&exported_type)) { GType type_id; const GTypeInfo type_info = { sizeof (BoltExportedClass), bolt_exported_base_init, (GBaseFinalizeFunc) bolt_exported_base_finalize, (GClassInitFunc) bolt_exported_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (BoltExported), 0, /* n_preallocs */ bolt_exported_init, NULL, /* value_table */ }; type_id = g_type_register_static (G_TYPE_OBJECT, "BoltExported", &type_info, G_TYPE_FLAG_ABSTRACT); g_type_add_class_private (type_id, sizeof (BoltExportedClassPrivate)); BoltExported_private_offset = g_type_add_instance_private (type_id, sizeof (BoltExportedPrivate)); g_once_init_leave (&exported_type, type_id); } return exported_type; } enum { PROP_0, PROP_OBJECT_ID, PROP_OBJECT_PATH, PROP_EXPORTED, PROP_LAST }; static GParamSpec *props[PROP_LAST] = {NULL, }; enum { SIGNAL_AUTHORIZE_METHOD, SIGNAL_AUTHORIZE_PROPERTY, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0, }; static void bolt_exported_finalize (GObject *object) { BoltExported *exported = BOLT_EXPORTED (object); BoltExportedPrivate *priv = GET_PRIV (exported); if (bolt_exported_is_exported (exported)) bolt_exported_unexport (exported); g_clear_pointer (&priv->object_path, g_free); g_ptr_array_free (priv->props_changed, TRUE); G_OBJECT_CLASS (bolt_exported_parent_class)->finalize (object); } static void bolt_exported_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltExported *exported = BOLT_EXPORTED (object); BoltExportedPrivate *priv = GET_PRIV (exported); switch (prop_id) { case PROP_OBJECT_ID: bolt_bug ("BoltExported::object-id must be overridden"); break; case PROP_OBJECT_PATH: g_value_set_string (value, priv->object_path); break; case PROP_EXPORTED: g_value_set_boolean (value, priv->registration > 0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_exported_init (GTypeInstance *instance, gpointer g_class) { BoltExported *exported = BOLT_EXPORTED (instance); BoltExportedPrivate *priv = GET_PRIV (exported); priv->props_changed = g_ptr_array_new (); } static void bolt_exported_class_init (BoltExportedClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); bolt_exported_parent_class = g_type_class_peek_parent (klass); g_type_class_adjust_private_offset (klass, &BoltExported_private_offset); gobject_class->finalize = bolt_exported_finalize; gobject_class->get_property = bolt_exported_get_property; gobject_class->dispatch_properties_changed = bolt_exported_dispatch_properties_changed; klass->authorize_method = handle_authorize_method_default; klass->authorize_property = handle_authorize_property_default; props[PROP_OBJECT_ID] = g_param_spec_string ("object-id", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NICK); props[PROP_OBJECT_PATH] = g_param_spec_string ("object-path", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NICK); props[PROP_EXPORTED] = g_param_spec_boolean ("exported", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_NICK); g_object_class_install_properties (gobject_class, PROP_LAST, props); signals[SIGNAL_AUTHORIZE_METHOD] = g_signal_new ("authorize-method", BOLT_TYPE_EXPORTED, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BoltExportedClass, authorize_method), g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_BOOLEAN, 2, G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_POINTER); signals[SIGNAL_AUTHORIZE_PROPERTY] = g_signal_new ("authorize-property", BOLT_TYPE_EXPORTED, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BoltExportedClass, authorize_property), g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_BOOLEAN, 4, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_POINTER); } static void bolt_exported_base_init (gpointer g_class) { BoltExportedClass *klass = g_class; klass->priv = G_TYPE_CLASS_GET_PRIVATE (g_class, BOLT_TYPE_EXPORTED, BoltExportedClassPrivate); memset (klass->priv, 0, sizeof (BoltExportedClassPrivate)); klass->priv->methods = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, bolt_exported_method_free); klass->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, bolt_exported_prop_free); } static void bolt_exported_base_finalize (gpointer g_class) { BoltExportedClass *klass = g_class; BoltExportedClassPrivate *priv = klass->priv; if (priv->iface_info) { g_dbus_interface_info_unref (priv->iface_info); priv->iface_info = NULL; } g_hash_table_unref (priv->properties); g_hash_table_unref (priv->methods); g_clear_pointer (&priv->iface_name, g_free); g_clear_pointer (&priv->object_path, g_free); } /* internal utility functions */ static const char * bolt_exported_get_iface_name (BoltExported *exported) { BoltExportedClass *klass; klass = BOLT_EXPORTED_GET_CLASS (exported); return klass->priv->iface_name; } static BoltExportedProp * bolt_exported_lookup_property (BoltExported *exported, const char *name, GError **error) { BoltExportedClass *klass; BoltExportedProp *prop; if (name == NULL) return NULL; klass = BOLT_EXPORTED_GET_CLASS (exported); prop = g_hash_table_lookup (klass->priv->properties, name); if (prop == NULL) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "no such property: %s", name); return prop; } static BoltExportedMethod * bolt_exported_lookup_method (BoltExported *exported, const char *name, GError **error) { BoltExportedClass *klass; BoltExportedMethod *method; if (name == NULL) return NULL; klass = BOLT_EXPORTED_GET_CLASS (exported); method = g_hash_table_lookup (klass->priv->methods, name); if (method == NULL) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "no such method: %s", name); return method; } static GVariant * bolt_exported_get_prop (BoltExported *exported, BoltExportedProp *prop) { g_autoptr(GError) err = NULL; g_auto(GValue) res = G_VALUE_INIT; const char *name; const GParamSpec *spec; GVariant *ret; name = prop->name_obj; spec = prop->spec; g_value_init (&res, spec->value_type); g_object_get_property (G_OBJECT (exported), name, &res); ret = bolt_wire_conv_to_wire (prop->conv, &res, &err); if (ret == NULL) bolt_bug ("failed to serialize value for prop %s: %s", prop->spec->name, err->message); return ret; } static char * bolt_exported_make_object_path (BoltExported *exported) { g_autofree char *id = NULL; BoltExportedClass *klass; const char *base; klass = BOLT_EXPORTED_GET_CLASS (exported); base = klass->priv->object_path; g_object_get (exported, "object-id", &id, NULL); return bolt_gen_object_path (base, id); } /* dispatch helper function */ typedef struct _DispatchData { GDBusMethodInvocation *inv; gboolean is_property; union { BoltExportedMethod *method; BoltExportedProp *prop; }; } DispatchData; static void dispatch_data_free (DispatchData *data) { g_slice_free (DispatchData, data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (DispatchData, dispatch_data_free); static GVariant * dispach_property_setter (BoltExported *exported, GDBusMethodInvocation *inv, BoltExportedProp *prop, GError **error) { g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; g_autoptr(GVariant) vin = NULL; GVariant *params; gboolean ok; params = g_dbus_method_invocation_get_parameters (inv); g_variant_get_child (params, 2, "v", &vin); g_value_init (&val, prop->spec->value_type); ok = bolt_wire_conv_from_wire (prop->conv, vin, &val, &err); if (ok) ok = prop->setter (exported, prop->name_obj, &val, &err); if (!ok && err != NULL) { bolt_error_propagate (error, &err); return NULL; } else if (!ok) { bolt_critical (LOG_TOPIC ("dbus"), "property setter signaled error, but no error is set"); g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "%s", "could not set property"); return NULL; } g_object_notify_by_pspec (G_OBJECT (exported), prop->spec); return g_variant_new ("()"); } static GVariant * dispatch_method_call (BoltExported *exported, GDBusMethodInvocation *inv, BoltExportedMethod *method, GError **error) { GVariant *params = g_dbus_method_invocation_get_parameters (inv); GVariant *res; res = method->handler (exported, params, inv, error); return res; } static void query_authorization_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(DispatchData) data = user_data; GDBusMethodInvocation *inv = data->inv; BoltExported *exported = BOLT_EXPORTED (source_object); GVariant *ret; gboolean ok; ok = g_task_propagate_boolean (G_TASK (res), &err); bolt_debug (LOG_TOPIC ("dbus"), "authorization done: %s", bolt_yesno (ok)); if (!ok && err == NULL) { bolt_bug ("negative auth result, but no GError set"); g_set_error_literal (&err, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "access denied"); } if (!ok) { g_dbus_method_invocation_return_gerror (inv, err); return; } if (data->is_property) ret = dispach_property_setter (exported, inv, data->prop, &err); else ret = dispatch_method_call (exported, inv, data->method, &err); if (ret == NULL && err != NULL) g_dbus_method_invocation_return_gerror (inv, err); else if (ret != NULL) g_dbus_method_invocation_return_value (inv, ret); /* else: must have been handled by the method call directly */ } static void query_authorization (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { GError *error = NULL; BoltExported *exported = source_object; DispatchData *data = task_data; gboolean authorized = FALSE; if (data->is_property) { const char *method_name = g_dbus_method_invocation_get_method_name (data->inv); gboolean is_setter = bolt_streq (method_name, "Set"); g_signal_emit (exported, signals[SIGNAL_AUTHORIZE_PROPERTY], 0, data->prop->name_obj, is_setter, data->inv, &error, &authorized); } else { g_signal_emit (exported, signals[SIGNAL_AUTHORIZE_METHOD], 0, data->inv, &error, &authorized); } bolt_debug (LOG_TOPIC ("dbus"), "query_authorization returned: %s", bolt_yesno (authorized)); if (!authorized) g_task_return_error (task, error); else g_task_return_boolean (task, authorized); } static gboolean handle_authorize_method_default (BoltExported *exported, GDBusMethodInvocation *inv, GError **error) { const char *method_name; method_name = g_dbus_method_invocation_get_method_name (inv); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "bolt operation '%s' denied by default policy", method_name); return FALSE; } static gboolean handle_authorize_property_default (BoltExported *exported, const char *name, gboolean setting, GDBusMethodInvocation *inv, GError **error) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "setting property '%s' denied by default policy", name); return FALSE; } /* DBus virtual table */ static void handle_dbus_method_call (GDBusConnection *connection, const char *sender, const char *object_path, const char *interface_name, const char *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { g_autoptr(GTask) task = NULL; g_autoptr(GError) err = NULL; BoltExported *exported; gboolean is_property; DispatchData *data; exported = BOLT_EXPORTED (user_data); bolt_debug (LOG_TOPIC ("dbus"), "method call: %s.%s at %s from %s", interface_name, method_name, object_path, sender); /* we also handle property setting here */ is_property = bolt_streq (interface_name, "org.freedesktop.DBus.Properties"); data = g_slice_new0 (DispatchData); data->inv = invocation; data->is_property = is_property; if (is_property) { const GDBusPropertyInfo *pi; BoltExportedProp *prop = NULL; gboolean is_setter; pi = g_dbus_method_invocation_get_property_info (invocation); if (pi != NULL) prop = bolt_exported_lookup_property (exported, pi->name, &err); else g_set_error (&err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "property information missing"); is_setter = bolt_streq (method_name, "Set"); if (is_setter && prop != NULL && prop->setter == NULL) g_set_error (&err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "property: %s has no setter", prop->name_bus); data->prop = prop; } else { BoltExportedMethod *method; method = bolt_exported_lookup_method (exported, method_name, &err); data->method = method; } if (err != NULL) { //bolt_warn_err (err, LOG_TOPIC ("dbus"), "error dispatching call"); g_dbus_method_invocation_return_gerror (invocation, err); return; } task = g_task_new (exported, NULL, query_authorization_done, data); g_task_set_source_tag (task, handle_dbus_method_call); g_task_set_task_data (task, data, NULL); g_task_run_in_thread (task, query_authorization); } static GVariant * handle_dbus_get_property (GDBusConnection *connection, const char *sender, const char *object_path, const char *interface_name, const char *property_name, GError **error, gpointer user_data) { g_autoptr(GError) err = NULL; BoltExported *exported; BoltExportedProp *prop; GVariant *ret; exported = BOLT_EXPORTED (user_data); bolt_debug (LOG_TOPIC ("dbus"), "get property: %s.%s at %s from %s", interface_name, property_name, object_path, sender); prop = bolt_exported_lookup_property (exported, property_name, &err); if (prop == NULL) { bolt_warn_err (err, LOG_TOPIC ("dbus"), "get_property"); bolt_error_propagate (error, &err); return NULL; } ret = bolt_exported_get_prop (exported, prop); return ret; } static void bolt_exported_dispatch_properties_changed (GObject *object, guint n_pspecs, GParamSpec **pspecs) { g_autoptr(GVariant) changes = NULL; g_autoptr(GError) err = NULL; g_auto(GVariantBuilder) changed; g_auto(GVariantBuilder) invalidated; const char *iface_name; BoltExported *exported; BoltExportedPrivate *priv; gboolean ok; guint count = 0; exported = BOLT_EXPORTED (object); priv = GET_PRIV (exported); g_variant_builder_init (&changed, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_init (&invalidated, G_VARIANT_TYPE ("as")); /* no bus, no changed signal */ if (priv->dbus == NULL || priv->object_path == NULL) goto out; for (guint i = 0; i < n_pspecs; i++) { g_autoptr(GVariant) var = NULL; GParamSpec *pspec = pspecs[i]; BoltExportedProp *prop; const char *nick; nick = g_param_spec_get_nick (pspec); prop = bolt_exported_lookup_property (exported, nick, NULL); if (prop == NULL) { bolt_debug (LOG_TOPIC ("dbus"), "prop %s change ignored", nick); continue; } bolt_debug (LOG_TOPIC ("dbus"), "prop %s changed", nick); var = bolt_exported_get_prop (exported, prop); g_variant_builder_add (&changed, "{sv}", prop->name_bus, var); count++; } if (count == 0) goto out; iface_name = bolt_exported_get_iface_name (exported); changes = g_variant_ref_sink (g_variant_new ("(sa{sv}as)", iface_name, &changed, &invalidated)); ok = g_dbus_connection_emit_signal (priv->dbus, NULL, priv->object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", changes, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("dbus"), "error emitting property changes"); bolt_debug (LOG_TOPIC ("dbus"), "emitted property %u changes", count); out: CHAIN_UP (dispatch_properties_changed) (object, n_pspecs, pspecs); } static GDBusInterfaceVTable dbus_vtable = { handle_dbus_method_call, handle_dbus_get_property, NULL, /* set_property (handled by method call) */ }; /* public methods: class */ void bolt_exported_class_set_interface_name (BoltExportedClass *klass, const char *name) { g_return_if_fail (BOLT_IS_EXPORTED_CLASS (klass)); g_return_if_fail (klass->priv != NULL); g_return_if_fail (klass->priv->iface_name == NULL); klass->priv->iface_name = g_strdup (name); } void bolt_exported_class_set_interface_info (BoltExportedClass *klass, const char *iface_name, const char *resource_name) { g_autoptr(GError) err = NULL; GDBusInterfaceInfo *info = NULL; g_return_if_fail (BOLT_IS_EXPORTED_CLASS (klass)); g_return_if_fail (klass->priv != NULL); g_return_if_fail (klass->priv->iface_info == NULL); g_return_if_fail (iface_name != NULL); g_return_if_fail (resource_name != NULL); bolt_exported_class_set_interface_name (klass, iface_name); info = bolt_dbus_interface_info_lookup (resource_name, iface_name, &err); if (info == NULL) bolt_error (LOG_TOPIC ("dbus"), LOG_ERR (err), "could not set interface info"); klass->priv->iface_info = info; /* transfer ownership */ } void bolt_exported_class_set_object_path (BoltExportedClass *klass, const char *base_path) { g_return_if_fail (BOLT_IS_EXPORTED_CLASS (klass)); g_return_if_fail (klass->priv != NULL); g_return_if_fail (klass->priv->object_path == NULL); g_return_if_fail (base_path != NULL); g_return_if_fail (g_variant_is_object_path (base_path)); klass->priv->object_path = g_strdup (base_path); } void bolt_exported_class_export_property (BoltExportedClass *klass, GParamSpec *spec) { BoltExportedClassPrivate *priv; GDBusInterfaceInfo *iface = NULL; GDBusPropertyInfo *info = NULL; GDBusPropertyInfo **iter; BoltExportedProp *prop; const char *name_bus, *name_obj; if (!klass || !BOLT_IS_EXPORTED_CLASS (klass)) { bolt_error (LOG_TOPIC ("dbus"), "klass not a BoltExportedClass"); return; } g_return_if_fail (G_IS_PARAM_SPEC (spec)); priv = klass->priv; name_obj = g_param_spec_get_name (spec); name_bus = g_param_spec_get_nick (spec); iface = priv->iface_info; for (iter = iface->properties; iter && *iter; iter++) { GDBusPropertyInfo *pi = *iter; if (bolt_streq (pi->name, name_bus)) { info = pi; break; } } if (info == NULL) { bolt_error (LOG_TOPIC ("dbus"), "no property info for %s", name_bus); return; } prop = g_new0 (BoltExportedProp, 1); prop->spec = g_param_spec_ref (spec); prop->name_bus = name_bus; prop->name_obj = name_obj; prop->signature = g_variant_type_new (info->signature); prop->conv = bolt_wire_conv_for (prop->signature, prop->spec); bolt_debug (LOG_TOPIC ("dbus"), "installed prop: %s -> %s [%s]", prop->name_bus, prop->name_obj, bolt_wire_conv_describe (prop->conv)); g_hash_table_insert (priv->properties, (gpointer) prop->name_bus, prop); } void bolt_exported_class_export_properties (BoltExportedClass *klass, guint start, guint n_pspecs, GParamSpec **specs) { g_return_if_fail (BOLT_IS_EXPORTED_CLASS (klass)); g_return_if_fail (start > 0); for (guint i = start; i < n_pspecs; i++) bolt_exported_class_export_property (klass, specs[i]); } void bolt_exported_class_property_setter (BoltExportedClass *klass, GParamSpec *spec, BoltExportedSetter setter) { BoltExportedProp *prop; const char *nick; if (!klass || !BOLT_IS_EXPORTED_CLASS (klass)) { bolt_error (LOG_TOPIC ("dbus"), "klass not a BoltExportedClass"); return; } nick = g_param_spec_get_nick (spec); prop = g_hash_table_lookup (klass->priv->properties, nick); if (prop == NULL) { bolt_error (LOG_TOPIC ("dbus"), "unknown property: %s", nick); return; } prop->setter = setter; } void bolt_exported_class_property_wireconv (BoltExportedClass *klass, GParamSpec *spec, const char *custom_id, BoltConvToWire to_wire, BoltConvFromWire from_wire) { BoltExportedProp *prop; BoltWireConv *conv; const char *nick; g_return_if_fail (BOLT_IS_EXPORTED_CLASS (klass)); g_return_if_fail (G_IS_PARAM_SPEC (spec)); nick = g_param_spec_get_nick (spec); prop = g_hash_table_lookup (klass->priv->properties, nick); if (prop == NULL) { bolt_bug (LOG_TOPIC ("dbus"), "unknown property: %s", nick); return; } conv = bolt_wire_conv_custom (prop->signature, prop->spec, custom_id, to_wire, from_wire); bolt_wire_conv_unref (prop->conv); prop->conv = conv; /* transfer the reference */ bolt_debug (LOG_TOPIC ("dbus"), "+adjusted prop: wireconv: %s [%s]", prop->name_bus, bolt_wire_conv_describe (prop->conv)); } void bolt_exported_class_export_method (BoltExportedClass *klass, const char *name, BoltExportedMethodHandler handler) { BoltExportedMethod *method; g_return_if_fail (BOLT_IS_EXPORTED_CLASS (klass)); g_return_if_fail (name != NULL); g_return_if_fail (handler != NULL); method = g_new0 (BoltExportedMethod, 1); method->name = g_strdup (name); method->handler = handler; g_hash_table_insert (klass->priv->methods, method->name, method); } /* public methods: instance */ gboolean bolt_exported_export (BoltExported *exported, GDBusConnection *connection, const char *path_hint, GError **error) { g_autofree char *object_path = NULL; BoltExportedPrivate *priv; BoltExportedClass *klass; guint id; g_return_val_if_fail (BOLT_IS_EXPORTED (exported), FALSE); g_return_val_if_fail (connection != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); priv = GET_PRIV (exported); klass = BOLT_EXPORTED_GET_CLASS (exported); if (klass->priv->iface_info == NULL) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "interface information is missing"); return FALSE; } object_path = g_strdup (path_hint); if (object_path == NULL) { object_path = bolt_exported_make_object_path (exported); bolt_debug (LOG_TOPIC ("dbus"), "generated object path: %s", object_path); } if (object_path == NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, "object path empty for object"); return FALSE; } id = g_dbus_connection_register_object (connection, object_path, klass->priv->iface_info, &dbus_vtable, exported, NULL, error); if (id == 0) return FALSE; bolt_debug (LOG_TOPIC ("dbus"), "registered object at %s", object_path); priv->dbus = g_object_ref (connection); priv->object_path = g_steal_pointer (&object_path); priv->registration = id; g_object_notify_by_pspec (G_OBJECT (exported), props[PROP_OBJECT_PATH]); g_object_notify_by_pspec (G_OBJECT (exported), props[PROP_EXPORTED]); return TRUE; } gboolean bolt_exported_unexport (BoltExported *exported) { g_autofree char *opath = NULL; BoltExportedPrivate *priv; gboolean ok; g_return_val_if_fail (BOLT_IS_EXPORTED (exported), FALSE); priv = GET_PRIV (exported); if (priv->dbus == NULL || priv->registration == 0) return FALSE; ok = g_dbus_connection_unregister_object (priv->dbus, priv->registration); if (ok) { g_clear_object (&priv->dbus); priv->registration = 0; opath = g_steal_pointer (&priv->object_path); g_object_notify_by_pspec (G_OBJECT (exported), props[PROP_OBJECT_PATH]); g_object_notify_by_pspec (G_OBJECT (exported), props[PROP_EXPORTED]); } bolt_debug (LOG_TOPIC ("dbus"), "unregistered object at %s: %s", opath, bolt_yesno (ok)); return ok; } gboolean bolt_exported_is_exported (BoltExported *exported) { BoltExportedPrivate *priv; g_return_val_if_fail (BOLT_IS_EXPORTED (exported), FALSE); priv = GET_PRIV (exported); return priv->registration > 0; } GDBusConnection * bolt_exported_get_connection (BoltExported *exported) { BoltExportedPrivate *priv; g_return_val_if_fail (BOLT_IS_EXPORTED (exported), NULL); priv = GET_PRIV (exported); return priv->dbus; } const char * bolt_exported_get_object_path (BoltExported *exported) { BoltExportedPrivate *priv; g_return_val_if_fail (BOLT_IS_EXPORTED (exported), NULL); priv = GET_PRIV (exported); return priv->object_path; } gboolean bolt_exported_emit_signal (BoltExported *exported, const char *name, GVariant *parameters, GError **error) { g_autoptr(GError) err = NULL; BoltExportedPrivate *priv; const char *iface_name; gboolean ok; g_return_val_if_fail (BOLT_IS_EXPORTED (exported), FALSE); g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (parameters != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); priv = GET_PRIV (exported); /* if we are not exported, we just ignore this */ if (priv->dbus == NULL || priv->object_path == NULL) return TRUE; iface_name = bolt_exported_get_iface_name (exported); ok = g_dbus_connection_emit_signal (priv->dbus, NULL, priv->object_path, iface_name, name, parameters, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("dbus"), "error emitting signal"); bolt_error_propagate (error, &err); } else { bolt_debug (LOG_TOPIC ("dbus"), "emitted signal: %s", name); } return ok; } /* non BoltExported internal methods */ static void bolt_exported_method_free (gpointer data) { BoltExportedMethod *method = data; g_clear_pointer (&method->name, g_free); g_free (method); } static void bolt_exported_prop_free (gpointer data) { BoltExportedProp *prop = data; g_param_spec_unref (prop->spec); g_variant_type_free (prop->signature); bolt_wire_conv_unref (prop->conv); g_free (prop); } bolt-0.9.2/boltd/bolt-exported.h000066400000000000000000000120241417453051000165260ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include G_BEGIN_DECLS #define BOLT_TYPE_EXPORTED bolt_exported_get_type () G_DECLARE_DERIVABLE_TYPE (BoltExported, bolt_exported, BOLT, EXPORTED, GObject); typedef struct _BoltExportedClassPrivate BoltExportedClassPrivate; struct _BoltExportedClass { GObjectClass parent_class; /*< private >*/ BoltExportedClassPrivate *priv; /*< public >*/ /* Signals */ gboolean (*authorize_method) (BoltExported *exported, GDBusMethodInvocation *invocation, GError **error); gboolean (*authorize_property) (BoltExported *exported, const char *name, gboolean setting, GDBusMethodInvocation *invocation, GError **error); /* for the future */ gpointer padding[10]; }; typedef GVariant * (* BoltExportedMethodHandler) (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error); typedef gboolean (* BoltExportedSetter) (BoltExported *obj, const char *name, const GValue *value, GError **error); /* class methods */ void bolt_exported_class_set_interface_name (BoltExportedClass *klass, const char *name); void bolt_exported_class_set_interface_info (BoltExportedClass *klass, const char *iface_name, const char *resource_name); void bolt_exported_class_set_object_path (BoltExportedClass *klass, const char *base_path); void bolt_exported_class_export_property (BoltExportedClass *klass, GParamSpec *spec); void bolt_exported_class_export_properties (BoltExportedClass *klass, guint start, guint n_pspecs, GParamSpec **specs); void bolt_exported_class_property_setter (BoltExportedClass *klass, GParamSpec *spec, BoltExportedSetter setter); void bolt_exported_class_property_wireconv (BoltExportedClass *klass, GParamSpec *spec, const char *custom_id, BoltConvToWire to_wire, BoltConvFromWire from_wire); void bolt_exported_class_export_method (BoltExportedClass *klass, const char *name, BoltExportedMethodHandler handler); /* instance methods */ gboolean bolt_exported_export (BoltExported *exported, GDBusConnection *connection, const char *object_path, GError **error); gboolean bolt_exported_unexport (BoltExported *exported); gboolean bolt_exported_is_exported (BoltExported *exported); GDBusConnection * bolt_exported_get_connection (BoltExported *exported); const char * bolt_exported_get_object_path (BoltExported *exported); gboolean bolt_exported_emit_signal (BoltExported *exported, const char *name, GVariant *parameters, GError **error); void bolt_exported_flush (BoltExported *exported); G_END_DECLS bolt-0.9.2/boltd/bolt-guard.c000066400000000000000000000360151417453051000157770ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-error.h" #include "bolt-guard.h" #include "bolt-io.h" #include "bolt-log.h" #include "bolt-str.h" #include "bolt-unix.h" typedef enum GuardState { GUARD_STATE_ACTIVE = 0, GUARD_STATE_RELEASED = 1 } GuardState; /* BoltGuard */ static void bolt_guard_remove (BoltGuard *guard); struct _BoltGuard { GObject object; /* book-keeping */ GuardState state; char *path; char *fifo; guint watch; /* properties */ char *id; char *who; pid_t pid; }; enum { PROP_GUARD_0, PROP_PATH, PROP_FIFO, PROP_ID, PROP_WHO, PROP_PID, PROP_GUARD_LAST }; static GParamSpec *guard_props[PROP_GUARD_LAST] = { NULL, }; enum { GUARD_SIGNAL_RELEASED, SIGNAL_LAST }; static guint guard_signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE (BoltGuard, bolt_guard, G_TYPE_OBJECT); static void bolt_guard_dispose (GObject *object) { BoltGuard *guard = BOLT_GUARD (object); /* remove our state file */ bolt_guard_remove (guard); if (guard->state != GUARD_STATE_RELEASED) { /* signal to clients to that we have been released. * NB: we must be intact for method call */ guard->state = GUARD_STATE_RELEASED; g_signal_emit (object, guard_signals[GUARD_SIGNAL_RELEASED], 0); } G_OBJECT_CLASS (bolt_guard_parent_class)->dispose (object); } static void bolt_guard_finalize (GObject *object) { BoltGuard *guard = BOLT_GUARD (object); if (guard->watch) g_source_remove (guard->watch); g_clear_pointer (&guard->path, g_free); g_clear_pointer (&guard->fifo, g_free); g_clear_pointer (&guard->who, g_free); g_clear_pointer (&guard->id, g_free); G_OBJECT_CLASS (bolt_guard_parent_class)->finalize (object); } static void bolt_guard_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltGuard *guard = BOLT_GUARD (object); switch (prop_id) { case PROP_PATH: g_value_set_string (value, guard->path); break; case PROP_FIFO: g_value_set_string (value, guard->fifo); break; case PROP_ID: g_value_set_string (value, guard->id); break; case PROP_WHO: g_value_set_string (value, guard->who); break; case PROP_PID: g_value_set_ulong (value, guard->pid); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_guard_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltGuard *guard = BOLT_GUARD (object); switch (prop_id) { case PROP_PATH: guard->path = g_value_dup_string (value); break; case PROP_FIFO: guard->fifo = g_value_dup_string (value); break; case PROP_ID: guard->id = g_value_dup_string (value); break; case PROP_WHO: guard->who = g_value_dup_string (value); break; case PROP_PID: guard->pid = g_value_get_ulong (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_guard_init (BoltGuard *guard) { guard->state = GUARD_STATE_ACTIVE; } static void bolt_guard_class_init (BoltGuardClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = bolt_guard_dispose; gobject_class->finalize = bolt_guard_finalize; gobject_class->get_property = bolt_guard_get_property; gobject_class->set_property = bolt_guard_set_property; guard_props[PROP_PATH] = g_param_spec_string ("path", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); guard_props[PROP_FIFO] = g_param_spec_string ("fifo", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); guard_props[PROP_ID] = g_param_spec_string ("id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); guard_props[PROP_WHO] = g_param_spec_string ("who", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); guard_props[PROP_PID] = g_param_spec_ulong ("pid", NULL, NULL, 0, G_MAXULONG, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_GUARD_LAST, guard_props); guard_signals[GUARD_SIGNAL_RELEASED] = g_signal_new ("released", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void bolt_guard_remove (BoltGuard *guard) { g_autoptr(GError) err = NULL; gboolean ok; /* we are not saved */ if (guard->path == NULL) return; /* */ if (guard->fifo != NULL) { bolt_debug (LOG_TOPIC ("guard"), "not removing guard '%s' with active fifo", guard->id); return; } ok = bolt_unlink (guard->path, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("guard"), "Could not remove power guard: '%s' @ %s", guard->id, guard->path); return; } g_clear_pointer (&guard->path, g_free); g_object_notify_by_pspec (G_OBJECT (guard), guard_props[PROP_PATH]); } static void bolt_guard_fifo_cleanup (BoltGuard *guard) { g_autoptr(GError) err = NULL; gboolean ok; if (guard->fifo == NULL) return; ok = bolt_unlink (guard->fifo, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("guard"), "Could not remove FIFO for power guard: '%s' @ %s", guard->id, guard->fifo); } g_clear_pointer (&guard->fifo, g_free); g_object_notify_by_pspec (G_OBJECT (guard), guard_props[PROP_FIFO]); } static gboolean bolt_guard_mkfifo (BoltGuard *guard, GError **error) { g_autoptr(GError) err = NULL; int r; g_return_val_if_fail (BOLT_IS_GUARD (guard), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (guard->fifo == NULL) guard->fifo = g_strdup_printf ("%s.fifo", guard->path); r = bolt_mkfifo (guard->fifo, 0600, &err); if (r == -1 && !bolt_err_exists (err)) return bolt_error_propagate (error, &err); g_object_notify_by_pspec (G_OBJECT (guard), guard_props[PROP_FIFO]); return TRUE; } static gboolean guard_has_event (GIOChannel *source, GIOCondition condition, gpointer data) { BoltGuard *guard = data; bolt_info (LOG_TOPIC ("guard"), "got event for guard '%s' (%x)", guard->id, (guint) condition); guard->watch = 0; return FALSE; } static void guard_watch_release (gpointer data) { BoltGuard *guard = data; if (guard->state == GUARD_STATE_RELEASED) /* if we are already released, the FIFO was kept * alive on purpose, so do nothing here */ return; if (guard->fifo == NULL) { bolt_bug (LOG_TOPIC ("guard"), "FIFO event but no FIFO"); return; } bolt_guard_fifo_cleanup (guard); bolt_debug (LOG_TOPIC ("guard"), "released watch reference for guard '%s'", guard->id); g_object_unref (guard); } int bolt_guard_monitor (BoltGuard *guard, GError **error) { g_autoptr(GIOChannel) ch = NULL; gboolean ok; int fd; g_return_val_if_fail (BOLT_IS_GUARD (guard), -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); ok = bolt_guard_mkfifo (guard, error); if (!ok) return -1; /* reader */ fd = bolt_open (guard->fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK, 0, error); if (fd == -1) return -1; ch = g_io_channel_unix_new (fd); g_io_channel_set_close_on_unref (ch, TRUE); g_io_channel_set_encoding (ch, NULL, NULL); g_io_channel_set_buffered (ch, FALSE); g_io_channel_set_flags (ch, G_IO_FLAG_NONBLOCK, NULL); fd = -1; /* the GIOChannel owns the fd, via _close_on_unref () */ (void) fd; /* The above is an intentional dead store */ /* writer */ fd = bolt_open (guard->fifo, O_WRONLY | O_CLOEXEC | O_NONBLOCK, 0, error); if (fd == -1) return -1; /* NB: we take a ref to the guard here */ guard->watch = g_io_add_watch_full (ch, G_PRIORITY_DEFAULT, G_IO_HUP | G_IO_ERR, guard_has_event, g_object_ref (guard), guard_watch_release); return fd; } const char * bolt_guard_get_id (BoltGuard *guard) { g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); return guard->id; } const char * bolt_guard_get_who (BoltGuard *guard) { g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); return guard->who; } guint bolt_guard_get_pid (BoltGuard *guard) { g_return_val_if_fail (BOLT_IS_GUARD (guard), 0); return (guint) guard->pid; } const char * bolt_guard_get_path (BoltGuard *guard) { g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); return guard->path; } const char * bolt_guard_get_fifo (BoltGuard *guard) { g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); return guard->fifo; } GPtrArray * bolt_guard_recover (const char *statedir, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GDir) dir = NULL; g_autoptr(GPtrArray) guards = NULL; const char *name; g_return_val_if_fail (statedir != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); dir = g_dir_open (statedir, 0, error); if (dir == NULL) return NULL; guards = g_ptr_array_new_with_free_func (g_object_unref); while ((name = g_dir_read_name (dir)) != NULL) { g_autoptr(BoltGuard) guard = NULL; int fd; if (!g_str_has_suffix (name, ".guard")) continue; guard = bolt_guard_load (statedir, name, &err); if (guard == NULL) { bolt_warn_err (err, LOG_TOPIC ("guard"), "could not load guard '%s'", name); g_clear_error (&err); continue; } /* internal guards are discarded */ if (guard->fifo == NULL) { bolt_info (LOG_TOPIC ("guard"), "ignoring guard '%s' for '%s': no fifo", guard->id, guard->who); continue; } else if (!bolt_pid_is_alive (guard->pid)) { bolt_info (LOG_TOPIC ("guard"), "ignoring guard '%s' for '%s': process dead", guard->id, guard->who); bolt_guard_fifo_cleanup (guard); continue; } fd = bolt_guard_monitor (guard, &err); if (fd < 0) { bolt_warn_err (err, "could not monitor guard '%d'", guard->id); g_clear_error (&err); continue; } /* close the write side */ (void) close (fd); /* monitoring adds a reference that we don't want */ g_object_unref (guard); g_ptr_array_add (guards, guard); guard = NULL; } return g_steal_pointer (&guards); } gboolean bolt_guard_save (BoltGuard *guard, GFile *guarddir, GError **error) { g_autoptr(GFile) guardfile = NULL; g_autoptr(GKeyFile) kf = NULL; g_autofree char *path = NULL; g_autofree char *name = NULL; gboolean ok; g_return_val_if_fail (guard->path == NULL, FALSE); name = g_strdup_printf ("%s.guard", guard->id); guardfile = g_file_get_child (guarddir, name); path = g_file_get_path (guardfile); kf = g_key_file_new (); g_key_file_set_string (kf, "guard", "id", guard->id); g_key_file_set_string (kf, "guard", "who", guard->who); g_key_file_set_uint64 (kf, "guard", "pid", guard->pid); ok = g_key_file_save_to_file (kf, path, error); if (ok) { guard->path = g_steal_pointer (&path); g_object_notify_by_pspec (G_OBJECT (guard), guard_props[PROP_PATH]); } return ok; } BoltGuard * bolt_guard_load (const char *statedir, const char *name, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GKeyFile) kf = NULL; g_autofree char *path = NULL; g_autofree char *who = NULL; g_autofree char *id = NULL; g_autofree char *fifo = NULL; gboolean ok; gulong pid; path = g_build_filename (statedir, name, NULL); kf = g_key_file_new (); ok = g_key_file_load_from_file (kf, path, 0, error); if (!ok) return NULL; id = g_key_file_get_string (kf, "guard", "id", &err); if (id == NULL) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "could not read 'id' field: %s", err->message); return NULL; } who = g_key_file_get_string (kf, "guard", "who", &err); if (who == NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, "field missing ('who')"); return NULL; } pid = (gulong) g_key_file_get_uint64 (kf, "guard", "pid", &err); if (err != NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, "field missing ('pid')"); return NULL; } fifo = g_strdup_printf ("%s.fifo", path); if (!g_file_test (fifo, G_FILE_TEST_EXISTS)) g_clear_pointer (&fifo, g_free); return g_object_new (BOLT_TYPE_GUARD, "path", path, "fifo", fifo, "id", id, "who", who, "pid", pid, NULL); } bolt-0.9.2/boltd/bolt-guard.h000066400000000000000000000035521417453051000160040ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-exported.h" #include G_BEGIN_DECLS #define BOLT_TYPE_GUARD bolt_guard_get_type () G_DECLARE_FINAL_TYPE (BoltGuard, bolt_guard, BOLT, GUARD, GObject); int bolt_guard_monitor (BoltGuard *guard, GError **error); const char * bolt_guard_get_id (BoltGuard *guard); const char * bolt_guard_get_who (BoltGuard *guard); guint bolt_guard_get_pid (BoltGuard *guard); const char * bolt_guard_get_path (BoltGuard *guard); const char * bolt_guard_get_fifo (BoltGuard *guard); GPtrArray * bolt_guard_recover (const char *statedir, GError **error); gboolean bolt_guard_save (BoltGuard *guard, GFile *guarddir, GError **error); BoltGuard * bolt_guard_load (const char *statedir, const char *name, GError **error); G_END_DECLS bolt-0.9.2/boltd/bolt-journal.c000066400000000000000000000342621417453051000163510ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-journal.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-io.h" #include "bolt-log.h" #include "bolt-macros.h" #include "bolt-str.h" #include #include #include /* ************************************ */ /* BoltJournal */ static void bolt_journal_initable_iface_init (GInitableIface *iface); static gboolean bolt_journal_initialize (GInitable *initable, GCancellable *cancellable, GError **error); struct _BoltJournal { GObject object; GFile *root; char *name; GFile *path; gboolean fresh; int fd; /* serials */ gint64 sl_time; guint32 sl_count; }; enum { PROP_JOURNAL_0, PROP_ROOT, PROP_NAME, PROP_FRESH, PROP_JOURNAL_LAST }; static GParamSpec *journal_props[PROP_JOURNAL_LAST] = { NULL, }; G_DEFINE_TYPE_WITH_CODE (BoltJournal, bolt_journal, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, bolt_journal_initable_iface_init)); static void bolt_journal_finalize (GObject *object) { BoltJournal *journal = BOLT_JOURNAL (object); if (journal->fd > -1) bolt_close (journal->fd, NULL); g_clear_object (&journal->root); g_clear_pointer (&journal->name, g_free); g_clear_object (&journal->path); G_OBJECT_CLASS (bolt_journal_parent_class)->finalize (object); } static void bolt_journal_init (BoltJournal *journal) { journal->fd = -1; } static void bolt_journal_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltJournal *journal = BOLT_JOURNAL (object); switch (prop_id) { case PROP_ROOT: g_value_set_object (value, journal->root); break; case PROP_NAME: g_value_set_string (value, journal->name); break; case PROP_FRESH: g_value_set_boolean (value, journal->fresh); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_journal_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltJournal *journal = BOLT_JOURNAL (object); switch (prop_id) { case PROP_ROOT: journal->root = g_value_dup_object (value); break; case PROP_NAME: journal->name = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_journal_class_init (BoltJournalClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = bolt_journal_finalize; gobject_class->get_property = bolt_journal_get_property; gobject_class->set_property = bolt_journal_set_property; journal_props[PROP_ROOT] = g_param_spec_object ("root", NULL, NULL, G_TYPE_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); journal_props[PROP_NAME] = g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); journal_props[PROP_FRESH] = g_param_spec_boolean ("fresh", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_JOURNAL_LAST, journal_props); } static void bolt_journal_initable_iface_init (GInitableIface *iface) { iface->init = bolt_journal_initialize; } static gboolean bolt_journal_initialize (GInitable *initable, GCancellable *cancellable, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *path = NULL; g_autofree char *size = NULL; bolt_autoclose int fd = -1; struct stat st; BoltJournal *journal; gboolean ok; journal = BOLT_JOURNAL (initable); if (bolt_strzero (journal->name) || journal->root == NULL) { bolt_bug ("invalid arguments"); g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "root and/or name NULL for journal"); return FALSE; } journal->path = g_file_get_child (journal->root, journal->name); ok = bolt_fs_make_parent_dirs (journal->path, &err); if (!ok && !bolt_err_exists (err)) return bolt_error_propagate (error, &err); path = g_file_get_path (journal->path); fd = bolt_open (path, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0666, error); if (fd < 0) return FALSE; memset (&st, 0, sizeof (st)); ok = bolt_fstat (fd, &st, &err); if (!ok) { g_propagate_prefixed_error (error, g_steal_pointer (&err), "could not read from journal: "); return FALSE; } size = g_format_size ((guint64) st.st_size); bolt_info (LOG_TOPIC ("journal"), "opened for '%.13s'; size: %s", journal->name, size); journal->fresh = st.st_size == 0; journal->fd = bolt_steal (&fd, -1); bolt_debug (LOG_TOPIC ("journal"), "fresh: %s, fd: %d", bolt_yesno (journal->fresh), journal->fd); return TRUE; } /* internal methods */ static gboolean bolt_journal_write_entry (int fd, const char *id, BoltJournalOp op, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *data = NULL; const char *opstr; gboolean ok; guint64 now; size_t l; g_return_val_if_fail (fd > -1, FALSE); now = (guint64) g_get_real_time (); opstr = bolt_journal_op_to_string (op); data = g_strdup_printf ("%s %s %016"G_GINT64_MODIFIER "X\n", id, opstr, now); l = strlen (data); ok = bolt_write_all (fd, data, l, &err); if (!ok) { g_propagate_prefixed_error (error, g_steal_pointer (&err), "could not add journal entry: "); return FALSE; } bolt_debug (LOG_TOPIC ("journal"), "wrote '%.*s' to %d", l - 1, data, fd); return TRUE; } static void bolt_journal_set_fresh (BoltJournal *journal, gboolean fresh) { if (journal->fresh == fresh) return; journal->fresh = fresh; g_object_notify_by_pspec (G_OBJECT (journal), journal_props[PROP_FRESH]); } /* public methods */ BoltJournal * bolt_journal_new (GFile *root, const char *name, GError **error) { return g_initable_new (BOLT_TYPE_JOURNAL, NULL, error, "root", root, "name", name, NULL); } gboolean bolt_journal_is_fresh (BoltJournal *journal) { g_return_val_if_fail (BOLT_IS_JOURNAL (journal), FALSE); return journal->fresh; } gboolean bolt_journal_put (BoltJournal *journal, const char *id, BoltJournalOp op, GError **error) { gboolean ok; int r; g_return_val_if_fail (BOLT_IS_JOURNAL (journal), FALSE); g_return_val_if_fail (id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = bolt_journal_write_entry (journal->fd, id, op, error); if (!ok) return FALSE; r = fdatasync (journal->fd); if (r == -1) { bolt_warn (LOG_TOPIC ("journal"), "could not flush (fdatasync) journal: %s", g_strerror (errno)); } bolt_journal_set_fresh (journal, FALSE); return TRUE; } gboolean bolt_journal_put_diff (BoltJournal *journal, GHashTable *diff, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *path = NULL; g_autofree char *base = NULL; bolt_autoclose int fd = -1; struct stat st; GHashTableIter iter; gpointer key, val; gboolean ok = TRUE; g_return_val_if_fail (BOLT_IS_JOURNAL (journal), FALSE); g_return_val_if_fail (diff != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); base = g_file_get_path (journal->path); path = g_strdup_printf ("%s.lock", base); fd = bolt_open (path, O_RDWR | O_CREAT | O_CLOEXEC | O_TRUNC, 0666, error); if (fd < 0) return FALSE; memset (&st, 0, sizeof (st)); ok = bolt_fstat (journal->fd, &st, &err); if (!ok) { g_propagate_prefixed_error (error, g_steal_pointer (&err), "could not query journal: "); return FALSE; } ok = bolt_lseek (journal->fd, 0, SEEK_SET, NULL, error); if (ok) ok = bolt_copy_bytes (journal->fd, fd, st.st_size, error); g_hash_table_iter_init (&iter, diff); while (ok && g_hash_table_iter_next (&iter, &key, &val)) { const char *uid = key; const int opcode = GPOINTER_TO_INT (val); BoltJournalOp op; switch (opcode) { case '+': op = BOLT_JOURNAL_ADDED; break; case '-': op = BOLT_JOURNAL_REMOVED; break; default: g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "unsupported op-code in diff: %c", opcode); return FALSE; } ok = bolt_journal_write_entry (fd, uid, op, error); } if (ok) ok = bolt_fdatasync (fd, error); if (ok) ok = bolt_faddflags (fd, O_APPEND, error); if (ok) ok = bolt_rename (path, base, error); if (!ok) return FALSE; bolt_swap (journal->fd, fd); bolt_journal_set_fresh (journal, FALSE); return TRUE; } GPtrArray * bolt_journal_list (BoltJournal *journal, GError **error) { g_autoptr(GInputStream) is = NULL; g_autoptr(GDataInputStream) ds = NULL; GPtrArray *res = NULL; off_t pos; g_return_val_if_fail (BOLT_IS_JOURNAL (journal), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); pos = lseek (journal->fd, 0, SEEK_SET); if (pos == (off_t) -1) { int code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "could not read from journal: %s", g_strerror (code)); return NULL; } res = g_ptr_array_new_full (16, (GDestroyNotify) bolt_journal_item_free); is = g_unix_input_stream_new (journal->fd, FALSE); ds = g_data_input_stream_new (is); g_return_val_if_fail (ds != NULL, res); for (;; ) { g_autoptr(GError) err = NULL; g_autofree char *l = NULL; g_autofree char *name = NULL; g_autofree char *opstr = NULL; BoltJournalItem *i; BoltJournalOp op; guint64 ts; int n; l = g_data_input_stream_read_line (ds, NULL, NULL, &err); if (l == NULL) { if (err) bolt_warn_err (err, LOG_TOPIC ("journal"), "error reading from journal"); break; } n = sscanf (l, "%ms %ms %016" G_GINT64_MODIFIER "X", &name, &opstr, &ts); if (n != 3) { bolt_warn (LOG_TOPIC ("journal"), "invalid entry: '%s'", l); continue; } op = bolt_journal_op_from_string (opstr, &err); if (err != NULL) { bolt_warn_err (err, LOG_TOPIC ("journal"), "skipping entry '%s'", l); continue; } i = g_slice_new (BoltJournalItem); i->id = g_strdup (name); i->ts = ts; i->op = op; g_ptr_array_add (res, i); } return res; } gboolean bolt_journal_reset (BoltJournal *journal, GError **error) { gboolean ok; g_return_val_if_fail (BOLT_IS_JOURNAL (journal), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = bolt_ftruncate (journal->fd, 0, error); if (ok) journal->fresh = TRUE; return ok; } /* journal op methods */ const char * bolt_journal_op_to_string (BoltJournalOp op) { switch (op) { case BOLT_JOURNAL_FAILED: return "!"; case BOLT_JOURNAL_UNCHANGED: return "="; case BOLT_JOURNAL_ADDED: return "+"; case BOLT_JOURNAL_REMOVED: return "-"; } bolt_warn_enum_unhandled (BoltJournalOp, op); return "?"; } BoltJournalOp bolt_journal_op_from_string (const char *data, GError **error) { g_return_val_if_fail (error == NULL || *error == NULL, BOLT_JOURNAL_FAILED); /* both will be caught as errors after * the switch statement */ if (data == NULL) data = ""; else if (*data == '\0') data = ""; switch (data[0]) { case '!': return BOLT_JOURNAL_FAILED; case '+': return BOLT_JOURNAL_ADDED; case '-': return BOLT_JOURNAL_REMOVED; case '=': return BOLT_JOURNAL_UNCHANGED; } g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "invalid journal operation: %s", data); return BOLT_JOURNAL_FAILED; } /*BoltJournalItem */ void bolt_journal_item_free (BoltJournalItem *entry) { g_free (entry->id); g_slice_free (BoltJournalItem, entry); } bolt-0.9.2/boltd/bolt-journal.h000066400000000000000000000047271417453051000163610ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include /* BoltJournal - database for devices, keys */ #define BOLT_TYPE_JOURNAL bolt_journal_get_type () G_DECLARE_FINAL_TYPE (BoltJournal, bolt_journal, BOLT, JOURNAL, GObject); typedef enum BoltJournalOp { BOLT_JOURNAL_FAILED = -1, BOLT_JOURNAL_UNCHANGED = '=', BOLT_JOURNAL_ADDED = '+', BOLT_JOURNAL_REMOVED = '-', } BoltJournalOp; typedef struct BoltJournalItem { char *id; BoltJournalOp op; guint64 ts; /* timestamp */ } BoltJournalItem; BoltJournal * bolt_journal_new (GFile *root, const char *name, GError **error); gboolean bolt_journal_is_fresh (BoltJournal *journal); gboolean bolt_journal_put (BoltJournal *journal, const char *id, BoltJournalOp op, GError **error); gboolean bolt_journal_put_diff (BoltJournal *journal, GHashTable *diff, GError **error); GPtrArray * bolt_journal_list (BoltJournal *journal, GError **error); gboolean bolt_journal_reset (BoltJournal *journal, GError **error); /* BoltJournalOp */ const char * bolt_journal_op_to_string (BoltJournalOp op); BoltJournalOp bolt_journal_op_from_string (const char *data, GError **error); /* BoltJournalItem */ void bolt_journal_item_free (BoltJournalItem *entry); bolt-0.9.2/boltd/bolt-key.c000066400000000000000000000151301417453051000154600ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-store.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-io.h" #include "bolt-key.h" #include "bolt-rnd.h" #include "bolt-str.h" #include #include /* ************************************ */ /* BoltKey */ struct _BoltKey { GObject object; /* the actual key plus the null char */ char data[BOLT_KEY_CHARS + 1]; gboolean fresh; }; enum { PROP_KEY_0, PROP_KEY_FRESH, PROP_KEY_LAST }; static GParamSpec *key_props[PROP_KEY_LAST] = { NULL, }; G_DEFINE_TYPE (BoltKey, bolt_key, G_TYPE_OBJECT); static void bolt_key_finalize (GObject *object) { BoltKey *key = BOLT_KEY (object); bolt_erase_n (key->data, sizeof (key->data)); G_OBJECT_CLASS (bolt_key_parent_class)->finalize (object); } static void bolt_key_init (BoltKey *key) { memset (key->data, 0, sizeof (key->data)); } static void bolt_key_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltKey *key = BOLT_KEY (object); switch (prop_id) { case PROP_KEY_FRESH: g_value_set_boolean (value, key->fresh); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_key_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltKey *key = BOLT_KEY (object); switch (prop_id) { case PROP_KEY_FRESH: key->fresh = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_key_class_init (BoltKeyClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = bolt_key_finalize; gobject_class->get_property = bolt_key_get_property; gobject_class->set_property = bolt_key_set_property; key_props[PROP_KEY_FRESH] = g_param_spec_boolean ("fresh", NULL, NULL, FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NICK); g_object_class_install_properties (gobject_class, PROP_KEY_LAST, key_props); } /* internal methods */ /* public methods */ BoltKey * bolt_key_new (GError **error) { BoltKey *key; BoltRng rng; char data[BOLT_KEY_BYTES]; rng = bolt_get_random_data (data, BOLT_KEY_BYTES); /* fail if we can not be sure that we have good enough * random data, which is only guaranteed by getrandom */ if (rng != BOLT_RNG_GETRANDOM) { g_set_error (error, BOLT_ERROR_FAILED, BOLT_ERROR_NOKEY, "failed to create key: no random data"); return NULL; } key = g_object_new (BOLT_TYPE_KEY, NULL); for (guint i = 0; i < BOLT_KEY_BYTES; i++) { char *pos = key->data + 2 * i; gulong n = sizeof (key->data) - 2 * i; g_snprintf (pos, n, "%02hhx", data[i]); } bolt_erase_n (data, sizeof (data)); key->fresh = TRUE; return key; } gboolean bolt_key_write_to (BoltKey *key, int fd, BoltSecurity *level, GError **error) { g_autoptr(GError) err = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_KEY (key), FALSE); g_return_val_if_fail (fd > -1, FALSE); g_return_val_if_fail (level != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); *level = BOLT_SECURITY_USER; if (key->data[0] == '\0') return TRUE; ok = bolt_write_all (fd, key->data, BOLT_KEY_CHARS, &err); if (!ok && g_error_matches (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_BADKEY, "invalid key data"); else if (!ok) bolt_error_propagate (error, &err); else if (!key->fresh) /* ok == True */ *level = BOLT_SECURITY_SECURE; return ok; } gboolean bolt_key_save_file (BoltKey *key, GFile *file, GError **error) { gboolean ok; ok = g_file_replace_contents (file, key->data, BOLT_KEY_CHARS, NULL, FALSE, G_FILE_CREATE_PRIVATE, NULL, NULL, error); return ok; } BoltKey * bolt_key_load_file (GFile *file, GError **error) { g_autoptr(BoltKey) key = NULL; g_autofree char *path = NULL; gboolean ok; gsize len; int fd; g_return_val_if_fail (G_IS_FILE (file), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); key = g_object_new (BOLT_TYPE_KEY, NULL); path = g_file_get_path (file); fd = bolt_open (path, O_CLOEXEC | O_RDONLY, 0, error); if (fd < 0) return NULL; memset (key->data, 0, sizeof (key->data)); ok = bolt_read_all (fd, key->data, BOLT_KEY_CHARS, &len, error); close (fd); if (!ok) return NULL; /* empty key; NB: the kernel gives us "\n" for an empty key */ if (len == 0 || (len == 1 && g_ascii_isspace (key->data[0]))) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_NOKEY, "key-file exists but contains no data"); return NULL; } if (len != BOLT_KEY_CHARS) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_BADKEY, "unexpected key size (corrupt key?): %zu", len); return NULL; } key->fresh = FALSE; return g_steal_pointer (&key); } BoltKeyState bolt_key_get_state (BoltKey *key) { if (key == NULL) return BOLT_KEY_MISSING; g_return_val_if_fail (BOLT_IS_KEY (key), BOLT_KEY_UNKNOWN); return key->fresh ? BOLT_KEY_NEW : BOLT_KEY_HAVE; } bolt-0.9.2/boltd/bolt-key.h000066400000000000000000000032321417453051000154650ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include "bolt-enums.h" G_BEGIN_DECLS /* BoltKey - represents a key to authorize devices with */ #define BOLT_TYPE_KEY bolt_key_get_type () G_DECLARE_FINAL_TYPE (BoltKey, bolt_key, BOLT, KEY, GObject); #define BOLT_KEY_BYTES 32 #define BOLT_KEY_CHARS 64 BoltKey * bolt_key_new (GError **error); gboolean bolt_key_write_to (BoltKey *key, int fd, BoltSecurity *level, GError **error); gboolean bolt_key_save_file (BoltKey *key, GFile *file, GError **error); BoltKey * bolt_key_load_file (GFile *file, GError **error); BoltKeyState bolt_key_get_state (BoltKey *key); G_END_DECLS bolt-0.9.2/boltd/bolt-log.c000066400000000000000000000421561417453051000154610ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-device.h" #include "bolt-domain.h" #include "bolt-error.h" #include "bolt-macros.h" #include "bolt-rnd.h" #include "bolt-str.h" #include "bolt-term.h" #include "bolt-log.h" #include #include #include #include #include /* mapping taking from glib */ typedef struct BoltLogLevel { GLogLevelFlags code; const char *prio; const char *name; } BoltLogLevel; BoltLogLevel known_levels[] = { {G_LOG_LEVEL_ERROR, "3", "error"}, {G_LOG_LEVEL_CRITICAL, "4", "critical"}, {G_LOG_LEVEL_WARNING, "4", "warning"}, {G_LOG_LEVEL_MESSAGE, "5", "message"}, {G_LOG_LEVEL_INFO, "6", "info"}, {G_LOG_LEVEL_DEBUG, "7", "debug"} }; const char * bolt_log_level_to_priority (GLogLevelFlags log_level) { for (gsize i = 0; i < G_N_ELEMENTS (known_levels); i++) if (known_levels[i].code & log_level) return known_levels[i].prio; /* Default to LOG_NOTICE for custom log levels. */ return "5"; } const char * bolt_log_level_to_string (GLogLevelFlags log_level) { for (gsize i = 0; i < G_N_ELEMENTS (known_levels); i++) if (known_levels[i].code & log_level) return known_levels[i].name; return "user"; } static FILE * log_level_to_file (GLogLevelFlags log_level) { return log_level & G_LOG_LEVEL_DEBUG ? stdout : stderr; } #define internal_error(fmt, ...) g_fprintf (stderr, "log-ERROR: " fmt "\n", __VA_ARGS__) static const char * bolt_color_for (FILE *out, const char *color) { int fd = fileno (out); return g_log_writer_supports_color (fd) ? color : ""; } struct _BoltLogCtx { BoltDevice *device; const GError *error; /* standard fields */ GLogField *self; GLogField *message; GLogField *priority; GLogField *domain; GLogField *topic; gboolean is_bug; /* field storage */ gsize n_fields; GLogField fields[32]; /* heap or stack */ gboolean allocated; }; static gboolean bolt_log_ctx_next_field (BoltLogCtx *ctx, GLogField **next) { const gsize maxfields = G_N_ELEMENTS (ctx->fields); gboolean res = TRUE; gsize n; /* safe to use as index */ /* make sure we are ALWAYS within bounds */ n = MIN (ctx->n_fields, maxfields - 1); *next = ctx->fields + n; res = ctx->n_fields < maxfields; ctx->n_fields += 1; if (!res) internal_error ("fields overflow. '%s' dropped", ctx->fields[n].key); return res; } #define BOLT_LOG_CTX_KEY "BOLT_LOG_CONTEXT" static gsize bolt_log_ctx_finish (BoltLogCtx *ctx) { GLogField *self; gboolean ok; ok = bolt_log_ctx_next_field (ctx, &self); if (!ok) { const gsize maxfields = sizeof (ctx->fields); gsize i = ctx->n_fields - maxfields; ctx->n_fields = maxfields; internal_error ("overflow of %" G_GSIZE_FORMAT, i); } self->key = BOLT_LOG_CTX_KEY; self->value = ctx; self->length = 0; ctx->self = self; return ctx->n_fields; } static gboolean bolt_log_ctx_find_field (const BoltLogCtx *ctx, const char *name, const GLogField **out) { g_return_val_if_fail (ctx != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (out != NULL, FALSE); for (gsize i = 0; i < ctx->n_fields; i++) { const GLogField *field = ctx->fields + i; if (bolt_streq (field->key, name)) { *out = field; return TRUE; } } return FALSE; } static void handle_domain_field (BoltLogCtx *ctx, const char *key, gpointer ptr) { BoltDomain *dom = BOLT_DOMAIN (ptr); const char *name; GLogField *field; g_return_if_fail (BOLT_IS_DOMAIN (dom)); bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_DOMAIN_UID; field->value = bolt_domain_get_uid (dom); field->length = -1; name = bolt_domain_get_id (dom); if (name == NULL) return; bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_DOMAIN_NAME; field->value = name; field->length = -1; } static void handle_device_field (BoltLogCtx *ctx, const char *key, gpointer ptr) { BoltDevice *dev = BOLT_DEVICE (ptr); BoltStatus status; GLogField *field; g_return_if_fail (BOLT_IS_DEVICE (dev)); ctx->device = dev; bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_DEVICE_UID; field->value = bolt_device_get_uid (dev); field->length = -1; bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_DEVICE_NAME; field->value = bolt_device_get_name (dev); field->length = -1; bolt_log_ctx_next_field (ctx, &field); status = bolt_device_get_status (dev); field->key = BOLT_LOG_DEVICE_STATE; field->value = bolt_status_to_string (status); field->length = -1; } static void handle_gerror_field (BoltLogCtx *ctx, const char *key, gpointer ptr) { const GError *error = ptr; GLogField *field; GError fallback = { .domain = BOLT_ERROR, .code = BOLT_ERROR_FAILED, .message = (char *) "unknown cause", }; ctx->error = error; if (error == NULL) { error = &fallback; ctx->is_bug = TRUE; } bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_ERROR_DOMAIN; field->value = g_quark_to_string (error->domain); field->length = -1; bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_ERROR_CODE; field->value = &error->code; field->length = sizeof (gint); bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_ERROR_MESSAGE; field->value = error->message; field->length = -1; } static void handle_topic_field (BoltLogCtx *ctx, const char *key, gpointer ptr) { const char *value = ptr; GLogField *field; bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_TOPIC; field->value = value; field->length = -1; ctx->topic = field; if (bolt_streq ((const char *) ptr, "code")) ctx->is_bug = TRUE; } struct SpecialField { const char *name; void (*handler) (BoltLogCtx *ctx, const char *key, gpointer ptr); } special_fields[] = { {"device", handle_device_field}, {"domain", handle_domain_field}, {"error", handle_gerror_field}, {"topic", handle_topic_field} }; static gboolean handle_special_field (BoltLogCtx *ctx, const char *key, gpointer ptr) { gboolean handled = FALSE; key++; /* remove the special key indicator */ for (gsize i = 0; i < G_N_ELEMENTS (special_fields); i++) { if (g_str_equal (key, special_fields[i].name)) { special_fields[i].handler (ctx, key, ptr); handled = TRUE; } } return handled; } static gboolean handle_passthrough_field (BoltLogCtx *ctx, const char *key, const char *val) { GLogField *field; key++; /* remove the pass-through key indicator */ bolt_log_ctx_next_field (ctx, &field); field->key = key; field->value = val; field->length = -1; return TRUE; } static void add_bug_marker (BoltLogCtx *ctx) { GLogField *field; bolt_log_ctx_next_field (ctx, &field); field->key = BOLT_LOG_BUG_MARK; field->value = "*"; field->length = -1; } void bolt_log (const char *domain, GLogLevelFlags level, ...) { va_list args; va_start (args, level); bolt_logv (domain, level, args); va_end (args); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" void bolt_logv (const char *domain, GLogLevelFlags level, va_list args) { BoltLogCtx ctx = {NULL, }; char message[1024] = {0, }; const char *key; bolt_log_ctx_next_field (&ctx, &ctx.message); bolt_log_ctx_next_field (&ctx, &ctx.priority); bolt_log_ctx_next_field (&ctx, &ctx.domain); while ((key = va_arg (args, const char *)) != NULL) { gboolean handled; if (*key == LOG_SPECIAL_CHAR) { gpointer ptr = va_arg (args, gpointer); handled = handle_special_field (&ctx, key, ptr); } else if (*key == LOG_PASSTHROUGH_CHAR) { const char *val = va_arg (args, const char *); handled = handle_passthrough_field (&ctx, key, val); } else { break; } if (!handled) internal_error ("unknown field: %s", key); } g_vsnprintf (message, sizeof (message), key ? : "", args); ctx.message->key = "MESSAGE"; ctx.message->value = message; ctx.message->length = -1; ctx.priority->key = "PRIORITY"; ctx.priority->value = bolt_log_level_to_priority (level); ctx.priority->length = -1; ctx.domain->key = "GLIB_DOMAIN"; ctx.domain->value = domain ? : "boltd"; ctx.domain->length = -1; if (ctx.is_bug) add_bug_marker (&ctx); bolt_log_ctx_finish (&ctx); /* pass it to the normal log mechanisms; * this should handle aborting on fatal * error messages for us. */ g_log_structured_array (level, ctx.fields, ctx.n_fields); } #pragma GCC diagnostic pop static char * format_uid_name (const char *uid, const char *name, char *buffer, gsize len, int size) { static const int u = 13; int n; n = MAX (0, size - u); g_snprintf (buffer, len, "%-.*s-%-*.*s", u, uid, n, n, name); return buffer; } static char * format_device_id (BoltDevice *device, char *buffer, gsize len, int size) { const char *uid; const char *name; uid = bolt_device_get_uid (device); name = bolt_device_get_name (device); format_uid_name (uid, name, buffer, len, size); return buffer; } #define TIME_MAXFMT 255 GLogWriterOutput bolt_log_stdstream (const BoltLogCtx *ctx, GLogLevelFlags log_level, guint flags) { FILE *out = log_level_to_file (log_level); const char *normal = bolt_color_for (out, ANSI_NORMAL); const char *gray = bolt_color_for (out, ANSI_HIGHLIGHT_BLACK); const char *blue = bolt_color_for (out, ANSI_BLUE); const char *fg = normal; const char *message; const GLogField *f; char the_time[TIME_MAXFMT]; struct tm lt; struct tm *tm; time_t now; g_return_val_if_fail (ctx != NULL, G_LOG_WRITER_UNHANDLED); g_return_val_if_fail (ctx->message != NULL, G_LOG_WRITER_UNHANDLED); time (&now); tm = localtime_r (&now, <); if (tm && strftime (the_time, sizeof (the_time), "%T", tm) > 0) g_fprintf (out, "%s%s%s ", gray, the_time, normal); if (log_level == G_LOG_LEVEL_CRITICAL || log_level == G_LOG_LEVEL_ERROR) fg = bolt_color_for (out, ANSI_RED); else if (log_level == G_LOG_LEVEL_WARNING) fg = bolt_color_for (out, ANSI_YELLOW); else if (log_level == G_LOG_LEVEL_DEBUG) fg = gray; if (ctx->device) { char name[64]; format_device_id (ctx->device, name, sizeof (name), 30); g_fprintf (out, "[%s%s%s] ", blue, name, normal); } else if (bolt_log_ctx_find_field (ctx, BOLT_LOG_DOMAIN_UID, &f)) { const char *uid = f->value; const char *name = "domain?"; char ident[64]; if (bolt_log_ctx_find_field (ctx, BOLT_LOG_DOMAIN_NAME, &f)) name = f->value; format_uid_name (uid, name, ident, sizeof (ident), 30); g_fprintf (out, "[%s%s%s] ", blue, ident, fg); } else if (bolt_log_ctx_find_field (ctx, BOLT_LOG_DEVICE_UID, &f)) { const char *uid = f->value; g_fprintf (out, "[%s%.13s %17s%s] ", blue, uid, " ", fg); } if (ctx->topic) g_fprintf (out, "%s%s%s: ", blue, (const char *) ctx->topic->value, fg); message = ctx->message->value; g_fprintf (out, "%s%s%s", fg, message, normal); if (ctx->error) { const char *yellow = bolt_color_for (out, ANSI_YELLOW); const char *msg = ctx->error->message; if (strlen (message) == 0) { const char *lvl = bolt_log_level_to_string (log_level); g_fprintf (out, "%s%s%s", fg, lvl, normal); } g_fprintf (out, ": %s%s%s", yellow, msg, normal); } g_fprintf (out, "\n"); fflush (out); return G_LOG_WRITER_HANDLED; } static int bolt_cat_printf (char **buffer, gsize *size, const char *fmt, ...) { va_list args; char *p = *buffer; gsize s = *size; int n; va_start (args, fmt); n = g_vsnprintf (p, s, fmt, args); va_end (args); /* TODO: check n */ *size = s - n; *buffer = p + n; return n; } void bolt_log_fmt_journal (const BoltLogCtx *ctx, GLogLevelFlags log_level, char *message, gsize size) { const char *m; char *p = message; const GLogField *f; g_return_if_fail (ctx != NULL && ctx->message != NULL); if (ctx->device) { char name[64]; format_device_id (ctx->device, name, sizeof (name), 40); bolt_cat_printf (&p, &size, "[%s] ", name); } else if (bolt_log_ctx_find_field (ctx, BOLT_LOG_DOMAIN_UID, &f)) { const char *uid = f->value; const char *name = "domain?"; char ident[64]; if (bolt_log_ctx_find_field (ctx, BOLT_LOG_DOMAIN_NAME, &f)) name = f->value; format_uid_name (uid, name, ident, sizeof (ident), 40); bolt_cat_printf (&p, &size, "[%s] ", ident); } else if (bolt_log_ctx_find_field (ctx, BOLT_LOG_DEVICE_UID, &f)) { const char *uid = f->value; bolt_cat_printf (&p, &size, "[%.13s %27s] ", uid, " "); } if (ctx->topic) bolt_cat_printf (&p, &size, "%s: ", (const char *) ctx->topic->value); m = ctx->message->value; bolt_cat_printf (&p, &size, "%s", m); if (ctx->error) { const GError *error = ctx->error; const char *msg = error->message; if (strlen (m) == 0) bolt_cat_printf (&p, &size, "%s", bolt_log_level_to_string (log_level)); bolt_cat_printf (&p, &size, ": %s", msg); } } GLogWriterOutput bolt_log_journal (const BoltLogCtx *ctx, GLogLevelFlags log_level, guint flags) { GLogWriterOutput res; char message[2048]; GLogField msg = {"MESSAGE", message, -1}; g_return_val_if_fail (ctx != NULL, G_LOG_WRITER_UNHANDLED); g_return_val_if_fail (ctx->message != NULL, G_LOG_WRITER_UNHANDLED); bolt_log_fmt_journal (ctx, log_level, message, sizeof (message)); *ctx->message = msg; res = g_log_writer_journald (log_level, ctx->fields, ctx->n_fields, NULL); return res; } BoltLogCtx * bolt_log_ctx_acquire (const GLogField *fields, gsize n) { BoltLogCtx *ctx; if (bolt_streq (fields[n - 1].key, BOLT_LOG_CTX_KEY)) { ctx = (BoltLogCtx *) fields[n - 1].value; /* sanity check */ if (ctx->message == NULL) return NULL; return ctx; } ctx = g_new0 (BoltLogCtx, 1); ctx->allocated = TRUE; for (gsize i = 0; i < n; i++) { GLogField *field = (GLogField *) &fields[i]; if (bolt_streq (field->key, "MESSAGE")) { bolt_log_ctx_next_field (ctx, &ctx->message); *(ctx->message) = *field; } else if (bolt_streq (field->key, "GLIB_DOMAIN")) { bolt_log_ctx_next_field (ctx, &ctx->domain); *(ctx->domain) = *field; } else if (bolt_streq (field->key, "PRIORITY")) { bolt_log_ctx_next_field (ctx, &ctx->priority); *(ctx->priority) = *field; } } if (ctx->message == NULL) { g_free (ctx); return NULL; } bolt_log_ctx_finish (ctx); return ctx; } gboolean bolt_log_ctx_set_id (BoltLogCtx *ctx, const char *id) { if (ctx->self == NULL) return FALSE; else if (ctx->self->length != 0) return FALSE; ctx->self->value = id; ctx->self->length = -1; return TRUE; } void bolt_log_ctx_free (BoltLogCtx *ctx) { if (ctx == NULL || ctx->allocated == FALSE) return; g_free (ctx); } const char * blot_log_ctx_get_domain (BoltLogCtx *ctx) { return ctx->domain ? ctx->domain->value : NULL; } void bolt_log_gen_id (char id[BOLT_LOG_MSG_IDLEN]) { guint8 data[16] = {0, }; static const char ch[16] = "0123456789abcdef"; bolt_get_random_data (&data, sizeof (data)); for (guint i = 0; i < 16; i++) { const guint8 b = data[i]; g_assert_cmpint ((b >> 4), <, sizeof (ch)); id[i * 2] = ch[b >> 4]; id[i * 2 + 1] = ch[b & 15]; } id[32] = '\0'; } bolt-0.9.2/boltd/bolt-log.h000066400000000000000000000157171417453051000154710ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-names.h" #include #include G_BEGIN_DECLS #define LOG_SPECIAL_CHAR '@' #define LOG_PASSTHROUGH_CHAR '_' #define LOG_DIRECT(k, v) "_" k, v #define LOG_DEV(device) "@device", device #define LOG_DOM(domain) "@domain", domain #define LOG_ERR(error) "@error", error #define LOG_TOPIC(topic) "@topic", topic #define LOG_DOM_UID(uid) LOG_DIRECT (BOLT_LOG_DOMAIN_UID, uid) #define LOG_DEV_UID(uid) LOG_DIRECT (BOLT_LOG_DEVICE_UID, uid) #define LOG_MSG_ID(msg_id) LOG_DIRECT ("MESSAGE_ID", msg_id) #define LOG_ID(id) LOG_MSG_ID (BOLT_LOG_MSG_ID_ ## id) #define bolt_debug(...) bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ __VA_ARGS__) #define bolt_info(...) bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ __VA_ARGS__) #define bolt_msg(...) bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ __VA_ARGS__) #define bolt_warn(...) bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ __VA_ARGS__) #define bolt_critical(...) bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ __VA_ARGS__) #define bolt_error(...) G_STMT_START { \ bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ __VA_ARGS__); \ for (;; ) { } \ } G_STMT_END #define bolt_warn_err(e, ...) bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ LOG_ERR (e), \ __VA_ARGS__) #define bolt_warn_enum_unhandled(the_enum, the_value) G_STMT_START { \ bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ LOG_TOPIC ("code"), \ "unhandled value of enum " #the_enum "%d", \ (int) the_value); \ } G_STMT_END #define bolt_bug(...) bolt_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \ LOG_DIRECT ("CODE_FILE", __FILE__), \ LOG_DIRECT ("CODE_LINE", G_STRINGIFY (__LINE__)), \ LOG_DIRECT ("CODE_FUNC", G_STRFUNC), \ LOG_TOPIC ("code"), \ __VA_ARGS__) void bolt_logv (const char *domain, GLogLevelFlags level, va_list args); void bolt_log (const char *domain, GLogLevelFlags level, ...); /* consumer functions */ const char * bolt_log_level_to_priority (GLogLevelFlags log_level); const char * bolt_log_level_to_string (GLogLevelFlags log_level); typedef struct _BoltLogCtx BoltLogCtx; BoltLogCtx * bolt_log_ctx_acquire (const GLogField *fields, gsize n); gboolean bolt_log_ctx_set_id (BoltLogCtx *ctx, const char *id); void bolt_log_ctx_free (BoltLogCtx *ctx); const char * blot_log_ctx_get_domain (BoltLogCtx *ctx); void bolt_log_fmt_journal (const BoltLogCtx *ctx, GLogLevelFlags log_level, char *message, gsize size); GLogWriterOutput bolt_log_stdstream (const BoltLogCtx *ctx, GLogLevelFlags log_level, guint flags); GLogWriterOutput bolt_log_journal (const BoltLogCtx *ctx, GLogLevelFlags log_level, guint flags); void bolt_log_gen_id (char id[BOLT_LOG_MSG_IDLEN]); G_DEFINE_AUTOPTR_CLEANUP_FUNC (BoltLogCtx, bolt_log_ctx_free); G_END_DECLS bolt-0.9.2/boltd/bolt-manager.c000066400000000000000000002350531417453051000163120ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-bouncer.h" #include "bolt-config.h" #include "bolt-device.h" #include "bolt-domain.h" #include "bolt-error.h" #include "bolt-log.h" #include "bolt-power.h" #include "bolt-store.h" #include "bolt-str.h" #include "bolt-sysfs.h" #include "bolt-time.h" #include "bolt-udev.h" #include "bolt-unix.h" #include "bolt-watchdog.h" #include "bolt-manager.h" #include #include #define PROBING_SETTLE_TIME_MS 2000 /* in milli-seconds */ typedef struct udev_device udev_device; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); static void bolt_manager_initable_iface_init (GInitableIface *iface); static gboolean bolt_manager_initialize (GInitable *initable, GCancellable *cancellable, GError **error); static gboolean bolt_manager_store_init (BoltManager *mgr, GError **error); static void bolt_manager_store_upgrade (BoltManager *mgr, gboolean *upgraded); /* internal manager functions */ static void manager_sd_notify_status (BoltManager *mgr); /* domain related functions */ static gboolean manager_load_domains (BoltManager *mgr, GError **error); static void manager_bootacl_inital_sync (BoltManager *mgr, BoltDomain *domain); static BoltDomain * manager_domain_ensure (BoltManager *mgr, struct udev_device *dev); static BoltDomain * manager_find_domain_by_syspath (BoltManager *mgr, const char *syspath); static void manager_register_domain (BoltManager *mgr, BoltDomain *domain); static void manager_deregister_domain (BoltManager *mgr, BoltDomain *domain); static void manager_cleanup_stale_domains (BoltManager *mgr); /* device related functions */ static gboolean manager_load_devices (BoltManager *mgr, GError **error); static void manager_register_device (BoltManager *mgr, BoltDevice *device); static void manager_deregister_device (BoltManager *mgr, BoltDevice *device); static BoltDevice * manager_find_device_by_syspath (BoltManager *mgr, const char *sysfs); static BoltDevice * manager_find_device_by_uid (BoltManager *mgr, const char *uid, GError **error); static BoltDevice * bolt_manager_get_parent (BoltManager *mgr, BoltDevice *dev); static GPtrArray * bolt_manager_get_children (BoltManager *mgr, BoltDevice *target); static void bolt_manager_label_device (BoltManager *mgr, BoltDevice *target); /* udev events */ static void handle_uevent_udev (BoltUdev *udev, const char *action, struct udev_device *device, gpointer user_data); static void handle_udev_domain_event (BoltManager *mgr, struct udev_device *device, const char *action); static void handle_udev_domain_removed (BoltManager *mgr, BoltDomain *domain); static void handle_udev_device_event (BoltManager *mgr, struct udev_device *device, const char *action); static void handle_udev_device_added (BoltManager *mgr, BoltDomain *domain, struct udev_device *udev); static void handle_udev_device_changed (BoltManager *mgr, BoltDevice *dev, struct udev_device *udev); static void handle_udev_device_removed (BoltManager *mgr, BoltDevice *dev); static void handle_udev_device_attached (BoltManager *mgr, BoltDomain *domain, BoltDevice *dev, struct udev_device *udev); static void handle_udev_device_detached (BoltManager *mgr, BoltDevice *dev); /* signal callbacks */ static void handle_store_device_added (BoltStore *store, const char *uid, BoltManager *mgr); static void handle_store_device_removed (BoltStore *store, const char *uid, BoltManager *mgr); static void handle_domain_security_changed (BoltManager *mgr, GParamSpec *unused, BoltDomain *domain); static void handle_device_status_changed (BoltDevice *dev, BoltStatus old, BoltManager *mgr); static void handle_device_generation_changed (BoltDevice *dev, GParamSpec *unused, BoltManager *mgr); static void handle_power_state_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data); /* acquiring indicator */ static void manager_probing_device_added (BoltManager *mgr, struct udev_device *dev); static void manager_probing_device_removed (BoltManager *mgr, struct udev_device *dev); static void manager_probing_domain_added (BoltManager *mgr, struct udev_device *domain); static void manager_probing_activity (BoltManager *mgr, gboolean weak); /* force powering */ static BoltGuard * manager_maybe_power_controller (BoltManager *mgr); /* config */ static void manager_load_user_config (BoltManager *mgr); /* dbus property setter */ static gboolean handle_set_authmode (BoltExported *obj, const char *name, const GValue *value, GError **error); /* dbus method calls */ static GVariant * handle_list_domains (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); static GVariant * handle_domain_by_id (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); static GVariant * handle_list_devices (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); static GVariant * handle_device_by_uid (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); static GVariant * handle_enroll_device (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); static GVariant * handle_forget_device (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); /* */ struct _BoltManager { BoltExported object; /* udev */ BoltUdev *udev; /* state */ BoltStore *store; BoltDomain *domains; GPtrArray *devices; BoltPower *power; BoltSecurity security; BoltAuthMode authmode; guint generation; /* policy enforcer */ BoltBouncer *bouncer; /* config */ GKeyFile *config; BoltPolicy policy; /* default enrollment policy, unless specified */ /* probing indicator */ guint authorizing; /* number of devices currently authorizing */ GPtrArray *probing_roots; /* pci device tree root */ guint probing_timeout; /* signal id & indicator */ gint64 probing_tstamp; /* time stamp of last activity */ guint probing_tsettle; /* how long to indicate after the last activity */ /* watchdog */ BoltWatchdog *dog; }; enum { PROP_0, PROP_VERSION, PROP_PROBING, PROP_POLICY, PROP_SECURITY, PROP_AUTHMODE, PROP_POWERSTATE, PROP_GENERATION, PROP_LAST, PROP_EXPORTED = PROP_VERSION }; static GParamSpec *props[PROP_LAST] = {NULL, }; G_DEFINE_TYPE_WITH_CODE (BoltManager, bolt_manager, BOLT_TYPE_EXPORTED, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, bolt_manager_initable_iface_init)); static void bolt_manager_finalize (GObject *object) { BoltManager *mgr = BOLT_MANAGER (object); g_clear_object (&mgr->udev); if (mgr->probing_timeout) { g_source_remove (mgr->probing_timeout); mgr->probing_timeout = 0; } g_clear_pointer (&mgr->probing_roots, g_ptr_array_unref); g_clear_object (&mgr->store); g_ptr_array_free (mgr->devices, TRUE); bolt_domain_clear (&mgr->domains); g_clear_pointer (&mgr->config, g_key_file_unref); g_clear_object (&mgr->power); g_clear_object (&mgr->bouncer); g_clear_object (&mgr->dog); G_OBJECT_CLASS (bolt_manager_parent_class)->finalize (object); } static void bolt_manager_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltManager *mgr = BOLT_MANAGER (object); switch (prop_id) { case PROP_VERSION: g_value_set_uint (value, BOLT_DBUS_API_VERSION); break; case PROP_PROBING: g_value_set_boolean (value, mgr->probing_timeout > 0); break; case PROP_POLICY: g_value_set_enum (value, mgr->policy); break; case PROP_SECURITY: g_value_set_enum (value, mgr->security); break; case PROP_AUTHMODE: g_value_set_flags (value, mgr->authmode); break; case PROP_POWERSTATE: g_value_set_enum (value, bolt_power_get_state (mgr->power)); break; case PROP_GENERATION: g_value_set_uint (value, mgr->generation); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_manager_init (BoltManager *mgr) { mgr->devices = g_ptr_array_new_with_free_func (g_object_unref); mgr->probing_roots = g_ptr_array_new_with_free_func (g_free); mgr->probing_tsettle = PROBING_SETTLE_TIME_MS; /* milliseconds */ mgr->security = BOLT_SECURITY_UNKNOWN; /* default configuration */ mgr->policy = BOLT_POLICY_AUTO; mgr->authmode = BOLT_AUTH_ENABLED; } static void bolt_manager_class_init (BoltManagerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); BoltExportedClass *exported_class = BOLT_EXPORTED_CLASS (klass); gobject_class->finalize = bolt_manager_finalize; gobject_class->get_property = bolt_manager_get_property; props[PROP_VERSION] = g_param_spec_uint ("version", "Version", "Version", 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_PROBING] = g_param_spec_boolean ("probing", "Probing", "Probing", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_POLICY] = g_param_spec_enum ("default-policy", "DefaultPolicy", "DefaultPolicy", BOLT_TYPE_POLICY, BOLT_POLICY_AUTO, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_SECURITY] = g_param_spec_enum ("security-level", "SecurityLevel", NULL, BOLT_TYPE_SECURITY, BOLT_SECURITY_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_AUTHMODE] = g_param_spec_flags ("auth-mode", "AuthMode", NULL, BOLT_TYPE_AUTH_MODE, BOLT_AUTH_ENABLED, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_POWERSTATE] = g_param_spec_enum ("power-state", "PowerState", NULL, BOLT_TYPE_POWER_STATE, BOLT_FORCE_POWER_UNSET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_GENERATION] = g_param_spec_uint ("generation", "Generation", NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); bolt_exported_class_set_interface_info (exported_class, BOLT_DBUS_INTERFACE, BOLT_DBUS_GRESOURCE_PATH); bolt_exported_class_export_properties (exported_class, PROP_EXPORTED, PROP_LAST, props); bolt_exported_class_property_setter (exported_class, props[PROP_AUTHMODE], handle_set_authmode); bolt_exported_class_export_method (exported_class, "ListDomains", handle_list_domains); bolt_exported_class_export_method (exported_class, "DomainById", handle_domain_by_id); bolt_exported_class_export_method (exported_class, "ListDevices", handle_list_devices); bolt_exported_class_export_method (exported_class, "DeviceByUid", handle_device_by_uid); bolt_exported_class_export_method (exported_class, "EnrollDevice", handle_enroll_device); bolt_exported_class_export_method (exported_class, "ForgetDevice", handle_forget_device); } static void bolt_manager_initable_iface_init (GInitableIface *iface) { iface->init = bolt_manager_initialize; } static gboolean bolt_manager_initialize (GInitable *initable, GCancellable *cancellable, GError **error) { g_autoptr(BoltGuard) power = NULL; BoltManager *mgr; struct udev_enumerate *enumerate; struct udev_list_entry *l, *devices; gboolean upgraded = FALSE; gboolean ok; mgr = BOLT_MANAGER (initable); /* store setup */ ok = bolt_manager_store_init (mgr, error); if (!ok) return FALSE; /* load dynamic user configuration */ manager_load_user_config (mgr); /* polkit setup */ mgr->bouncer = bolt_bouncer_new (cancellable, error); if (mgr->bouncer == NULL) return FALSE; bolt_bouncer_add_client (mgr->bouncer, mgr); /* watchdog setup */ mgr->dog = bolt_watchdog_new (error); if (mgr->dog == NULL) return FALSE; /* udev setup*/ bolt_info (LOG_TOPIC ("udev"), "initializing udev"); mgr->udev = bolt_udev_new ("udev", NULL, error); if (mgr->udev == NULL) return FALSE; g_signal_connect_object (mgr->udev, "uevent", (GCallback) handle_uevent_udev, mgr, 0); ok = manager_load_domains (mgr, error); if (!ok) return FALSE; ok = manager_load_devices (mgr, error); if (!ok) return FALSE; /* setup the power controller */ mgr->power = bolt_power_new (mgr->udev); bolt_bouncer_add_client (mgr->bouncer, mgr->power); g_signal_connect_object (mgr->power, "notify::state", G_CALLBACK (handle_power_state_changed), mgr, 0); /* if we don't see any tb device, we try to force power */ power = manager_maybe_power_controller (mgr); if (power != NULL) bolt_info (LOG_TOPIC ("manager"), "acquired power guard '%s'", bolt_guard_get_id (power)); /* TODO: error checking */ enumerate = bolt_udev_new_enumerate (mgr->udev, NULL); udev_enumerate_add_match_subsystem (enumerate, "thunderbolt"); /* only devices (i.e. not the domain controller) */ bolt_info (LOG_TOPIC ("udev"), "enumerating devices"); udev_enumerate_scan_devices (enumerate); devices = udev_enumerate_get_list_entry (enumerate); udev_list_entry_foreach (l, devices) { g_autoptr(GError) err = NULL; g_autoptr(udev_device) udevice = NULL; const char *syspath; const char *devtype; syspath = udev_list_entry_get_name (l); udevice = bolt_udev_device_new_from_syspath (mgr->udev, syspath, &err); if (udevice == NULL) { bolt_warn_err (err, "enumerating devices"); continue; } devtype = udev_device_get_devtype (udevice); if (bolt_streq (devtype, "thunderbolt_domain")) handle_udev_domain_event (mgr, udevice, "add"); if (bolt_streq (devtype, "thunderbolt_device")) handle_udev_device_event (mgr, udevice, "add"); } udev_enumerate_unref (enumerate); /* upgrade the store, if needed */ bolt_manager_store_upgrade (mgr, &upgraded); if (upgraded) manager_cleanup_stale_domains (mgr); manager_sd_notify_status (mgr); return TRUE; } static gboolean bolt_manager_store_init (BoltManager *mgr, GError **error) { bolt_info (LOG_TOPIC ("manager"), "initializing store"); mgr->store = bolt_store_new (bolt_get_store_path (), error); if (mgr->store == NULL) return FALSE; g_signal_connect_object (mgr->store, "device-added", G_CALLBACK (handle_store_device_added), mgr, 0); g_signal_connect_object (mgr->store, "device-removed", G_CALLBACK (handle_store_device_removed), mgr, 0); return TRUE; } static void bolt_manager_store_upgrade (BoltManager *mgr, gboolean *upgraded) { g_autoptr(GError) err = NULL; BoltStore *store = mgr->store; guint ver; gboolean ok; ver = bolt_store_get_version (store); if (ver == BOLT_STORE_VERSION) { bolt_debug (LOG_TOPIC ("store"), "store is up to date"); return; } bolt_info (LOG_TOPIC ("store"), "attempting upgrade from '%d'", ver); ok = bolt_store_upgrade (store, NULL, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("store"), "upgrade failed"); return; } ver = bolt_store_get_version (store); bolt_info (LOG_TOPIC ("store"), "upgraded to version '%d'", ver); *upgraded = TRUE; } /* internal functions */ static void manager_sd_notify_status (BoltManager *mgr) { g_autoptr(GError) err = NULL; g_autofree char *status = NULL; const char *pstate = NULL; BoltPowerState power; gboolean sent; gboolean authorizing; gboolean ok = FALSE; authorizing = bolt_flag_isset (mgr->authmode, BOLT_AUTH_ENABLED); power = bolt_power_get_state (mgr->power); pstate = bolt_enum_to_string (BOLT_TYPE_POWER_STATE, power, NULL); status = g_strdup_printf ("STATUS=authmode: %s," " force-power: %s", (authorizing ? "enabled" : "DISABLED"), (pstate ? : "unknown")); ok = bolt_sd_notify_literal (status, &sent, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("status"), "failed to send status"); else bolt_debug (LOG_TOPIC ("status"), "%s [sent: %s]", status, bolt_yesno (sent)); } static void manager_maybe_set_security (BoltManager *mgr, BoltSecurity security) { /* update the security level, if it is not already * set, but also ignore if security is 'unknown' */ if (security == BOLT_SECURITY_UNKNOWN) return; if (mgr->security == BOLT_SECURITY_UNKNOWN) { bolt_info ("security level set to '%s'", bolt_security_to_string (security)); mgr->security = security; g_object_notify_by_pspec (G_OBJECT (mgr), props[PROP_SECURITY]); } else if (mgr->security != security) { bolt_warn ("multiple security levels (%s vs %s)", bolt_security_to_string (mgr->security), bolt_security_to_string (security)); } } static void manager_maybe_set_generation (BoltManager *mgr, guint gen) { guint check; check = MAX (mgr->generation, gen); if (mgr->generation >= check) return; bolt_info ("global 'generation' set to '%d'", check); mgr->generation = check; g_object_notify_by_pspec (G_OBJECT (mgr), props[PROP_GENERATION]); } /* domain related function */ static gboolean manager_load_domains (BoltManager *mgr, GError **error) { g_auto(GStrv) ids = NULL; ids = bolt_store_list_uids (mgr->store, "domains", error); if (ids == NULL) { g_prefix_error (error, "failed to list domains in store: "); return FALSE; } bolt_info (LOG_TOPIC ("store"), "loading domains"); for (guint i = 0; i < g_strv_length (ids); i++) { g_autoptr(GError) err = NULL; BoltDomain *dom = NULL; const char *uid = ids[i]; bolt_info (LOG_TOPIC ("store"), LOG_DOM_UID (uid), "loading domain"); dom = bolt_store_get_domain (mgr->store, uid, &err); if (dom == NULL) { bolt_warn_err (err, LOG_DOM_UID (uid), LOG_TOPIC ("store"), "failed to load domain", uid); continue; } manager_register_domain (mgr, dom); } return TRUE; } static void manager_bootacl_inital_sync (BoltManager *mgr, BoltDomain *domain) { g_autoptr(GError) err = NULL; g_auto(GStrv) acl = NULL; gboolean ok; guint n, empty; if (!bolt_domain_supports_bootacl (domain)) { bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "bootacl not supported, no sync"); return; } acl = bolt_domain_dup_bootacl (domain); g_assert (acl != NULL); n = bolt_domain_bootacl_slots (domain, &empty); bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "sync start [slots: %u free: %u]", n, empty); for (guint i = 0; i < mgr->devices->len; i++) { BoltDevice *dev = g_ptr_array_index (mgr->devices, i); const char *duid = bolt_device_get_uid (dev); gboolean polok, inacl, sync; polok = bolt_device_get_policy (dev) == BOLT_POLICY_AUTO; inacl = bolt_domain_bootacl_contains (domain, duid); sync = polok && !inacl; bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (domain), LOG_DEV_UID (duid), "sync '%.13s…' %s [policy: %3s, in acl: %3s]", duid, bolt_yesno (sync), bolt_yesno (polok), bolt_yesno (inacl)); if (!sync) continue; bolt_domain_bootacl_allocate (domain, acl, duid); } ok = bolt_domain_bootacl_set (domain, acl, &err); if (!ok && err != NULL) { bolt_warn_err (err, LOG_DOM (domain), "failed to write bootacl"); return; } bolt_domain_bootacl_slots (domain, &empty); bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (domain), "sync done [wrote: %s, now free: %u]", bolt_yesno (ok), empty); } static gboolean domain_has_stable_uuid (BoltDomain *domain, struct udev_device *dev) { g_autoptr(GError) err = NULL; gboolean stable; gboolean ok; guint32 pci_id; /* On integrated TBT, like ICL/TGL, the uuid of the * controller is randomly generated on *every* boot, * and thus the uuid is not stable. */ /* default to FALSE, in case we have no entry */ stable = FALSE; ok = bolt_sysfs_nhi_id_for_domain (dev, &pci_id, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("udev"), LOG_DOM (domain), "failed to get NHI for domain"); return FALSE; } ok = bolt_nhi_uuid_is_stable (pci_id, &stable, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("udev"), LOG_DOM (domain), "failed to determine if uid is stable"); bolt_info (LOG_TOPIC ("udev"), LOG_DOM (domain), "uuid is stable: %s (for NHI: 0x%04x)", bolt_yesno (stable), pci_id); return stable; } static void manager_store_domain (BoltManager *mgr, BoltDomain *domain) { g_autoptr(GError) err = NULL; gboolean ok; bolt_info (LOG_TOPIC ("store"), LOG_DOM (domain), "storing newly connected domain"); ok = bolt_store_put_domain (mgr->store, domain, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DOM (domain), "could not store domain"); } static BoltDomain * manager_domain_ensure (BoltManager *mgr, struct udev_device *dev) { g_autoptr(GError) err = NULL; BoltSecurity level; BoltDomain *domain = NULL; GDBusConnection *bus; struct udev_device *host; struct udev_device *dom; const char *security; const char *syspath; const char *op; const char *uid; gboolean iommu; /* check if we already know a domain that is the parent * of the device (dev); if not then 'dev' is very likely * the host device. */ syspath = udev_device_get_syspath (dev); domain = manager_find_domain_by_syspath (mgr, syspath); if (domain != NULL) return domain; /* dev is very likely the host, i.e. root switch device, * but we might as well make sure we can get the domain * from any; also we make sure that we have indeed the * host device via this lookup. */ dom = bolt_sysfs_domain_for_device (dev, &host); if (dom == NULL) return NULL; uid = bolt_sysfs_device_get_unique_id (host, NULL); /* check if we have a stored domain with a matching uid * of the host device, if so then we have just connected * the corresponding domain controller, represented by * the 'dom' udev_device. */ domain = bolt_domain_find_id (mgr->domains, uid, NULL); if (domain != NULL) { bolt_domain_connected (domain, dom); return domain; } /* this is an unknown, unstored domain controller */ domain = bolt_domain_new_for_udev (dom, uid, &err); if (domain == NULL) { bolt_warn_err (err, LOG_TOPIC ("udev"), "failed to create domain: %s"); return NULL; } level = bolt_domain_get_security (domain); iommu = bolt_domain_has_iommu (domain); security = bolt_security_for_display (level, iommu); bolt_msg (LOG_DOM (domain), "newly connected [%s] (%s)", security, syspath); manager_maybe_set_security (mgr, level); manager_register_domain (mgr, domain); /* registering the domain will add one reference */ g_object_unref (domain); /* add all devices with POLICY_AUTO to the bootacl */ manager_bootacl_inital_sync (mgr, domain); /* now store the domain (with an updated bootacl), * but only if its uuid is the same across reboots */ if (domain_has_stable_uuid (domain, dom)) manager_store_domain (mgr, domain); /* export it on the bus and emit the added signals */ bus = bolt_exported_get_connection (BOLT_EXPORTED (mgr)); if (bus == NULL) return domain; bolt_domain_export (domain, bus); op = bolt_exported_get_object_path (BOLT_EXPORTED (domain)); bolt_exported_emit_signal (BOLT_EXPORTED (mgr), "DomainAdded", g_variant_new ("(o)", op), NULL); return domain; } static BoltDomain * manager_find_domain_by_syspath (BoltManager *mgr, const char *syspath) { BoltDomain *iter = mgr->domains; guint n_domains; n_domains = bolt_domain_count (mgr->domains); for (guint i = 0; i < n_domains; i++) { const char *prefix = bolt_domain_get_syspath (iter); /* we get a perfect match, if we search for the domain * itself, or if we are looking for the domain that * is the parent of the device in @syspath */ if (prefix && g_str_has_prefix (syspath, prefix)) return iter; iter = bolt_domain_next (iter); } return NULL; } static gboolean bootacl_alloc (BoltDomain *domain, GStrv acl, const char *uid, gint *slot, BoltManager *mgr) { char **target = NULL; guint64 last = G_MAXUINT64; /* if we have a valid allocation, just use that */ if (*slot != -1) return TRUE; g_return_val_if_fail (acl != NULL, FALSE); /* we need to replace one of devices currently in the acl */ for (char **iter = acl; *iter; iter++) { g_autoptr(BoltDevice) dev = NULL; guint64 ts; dev = manager_find_device_by_uid (mgr, uid, NULL); /* device might not be managed by us */ if (dev == NULL) continue; /* authtime is the last time a device was seen and * successfully authorized; choose the one that * has the *oldest* timestamp (smallest number) */ ts = bolt_device_get_authtime (dev); if (ts < last) { target = iter; last = ts; } } if (target == NULL) return FALSE; *slot = target - acl; return TRUE; } static void manager_register_domain (BoltManager *mgr, BoltDomain *domain) { guint n_slots, n_free; mgr->domains = bolt_domain_insert (mgr->domains, domain); n_slots = bolt_domain_bootacl_slots (domain, &n_free); bolt_info (LOG_TOPIC ("domain"), LOG_DOM (domain), "registered (bootacl: %u/%u)", n_free, n_slots); g_signal_connect_object (domain, "bootacl-alloc", G_CALLBACK (bootacl_alloc), mgr, 0); g_signal_connect_object (domain, "notify::security", G_CALLBACK (handle_domain_security_changed), mgr, G_CONNECT_SWAPPED); bolt_bouncer_add_client (mgr->bouncer, domain); } static void manager_deregister_domain (BoltManager *mgr, BoltDomain *domain) { bolt_info (LOG_TOPIC ("manager"), LOG_DOM (domain), "de-registered"); mgr->domains = bolt_domain_remove (mgr->domains, domain); } static void manager_cleanup_stale_domains (BoltManager *mgr) { g_autoptr(GPtrArray) domains = NULL; BoltDomain *iter; guint count; /* Before bolt 0.9.1, domains were stored that have an * unstable uuid, i.e. their uuids change on every boot * and thus there will be store entries that can't be * matched anymore and thus are "stale". */ bolt_info (LOG_TOPIC ("manager"), "stale domain cleanup"); count = bolt_domain_count (mgr->domains); domains = g_ptr_array_new_full (count, g_object_unref); iter = mgr->domains; for (guint i = 0; i < count; i++) { gboolean stored = bolt_domain_is_stored (iter); gboolean offline = !bolt_domain_is_connected (iter); if (stored && offline) g_ptr_array_add (domains, g_object_ref (iter)); iter = bolt_domain_next (iter); } for (guint i = 0; i < domains->len; i++) { g_autoptr(GError) err = NULL; BoltDomain *dom = g_ptr_array_index (domains, i); gboolean ok; bolt_info (LOG_DOM (dom), LOG_TOPIC ("store"), "stale domain detected"); ok = bolt_store_del_domain (mgr->store, dom, &err); if (!ok) { bolt_warn_err (err, LOG_DOM (dom), "failed to delete domain"); continue; } manager_deregister_domain (mgr, dom); } } /* device related functions */ static gboolean manager_load_devices (BoltManager *mgr, GError **error) { g_auto(GStrv) ids = NULL; ids = bolt_store_list_uids (mgr->store, "devices", error); if (ids == NULL) { g_prefix_error (error, "failed to list devices in store: "); return FALSE; } bolt_info (LOG_TOPIC ("store"), "loading devices"); for (guint i = 0; i < g_strv_length (ids); i++) { g_autoptr(GError) err = NULL; BoltDevice *dev = NULL; const char *uid = ids[i]; bolt_info (LOG_DEV_UID (uid), LOG_TOPIC ("store"), "loading device"); dev = bolt_store_get_device (mgr->store, uid, &err); if (dev == NULL) { bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DIRECT (BOLT_LOG_DEVICE_UID, uid), "failed to load device (%.7s)", uid); continue; } manager_register_device (mgr, dev); } return TRUE; } static void manager_register_device (BoltManager *mgr, BoltDevice *dev) { g_ptr_array_add (mgr->devices, dev); bolt_bouncer_add_client (mgr->bouncer, dev); g_signal_connect_object (dev, "status-changed", G_CALLBACK (handle_device_status_changed), mgr, 0); if (bolt_device_is_host (dev)) { guint generation = bolt_device_get_generation (dev); manager_maybe_set_generation (mgr, generation); g_signal_connect_object (dev, "notify::generation", G_CALLBACK (handle_device_generation_changed), mgr, 0); } } static void manager_deregister_device (BoltManager *mgr, BoltDevice *dev) { const char *opath; g_ptr_array_remove_fast (mgr->devices, dev); opath = bolt_device_get_object_path (dev); if (opath == NULL) return; bolt_exported_emit_signal (BOLT_EXPORTED (mgr), "DeviceRemoved", g_variant_new ("(o)", opath), NULL); bolt_device_unexport (dev); bolt_info (LOG_DEV (dev), LOG_TOPIC ("dbus"), "unexported"); } static BoltDevice * manager_find_device_by_syspath (BoltManager *mgr, const char *sysfs) { g_return_val_if_fail (sysfs != NULL, NULL); for (guint i = 0; i < mgr->devices->len; i++) { BoltDevice *dev = g_ptr_array_index (mgr->devices, i); const char *have = bolt_device_get_syspath (dev); if (bolt_streq (have, sysfs)) return g_object_ref (dev); } return NULL; } static BoltDevice * manager_find_device_by_uid (BoltManager *mgr, const char *uid, GError **error) { if (uid == NULL || uid[0] == '\0') { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "empty device unique_id"); return NULL; } for (guint i = 0; i < mgr->devices->len; i++) { BoltDevice *dev = g_ptr_array_index (mgr->devices, i); if (bolt_streq (bolt_device_get_uid (dev), uid)) return g_object_ref (dev); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "device with id '%s' could not be found.", uid); return NULL; } static BoltDevice * bolt_manager_get_parent (BoltManager *mgr, BoltDevice *dev) { g_autofree char *path = NULL; const char *syspath; const char *start; char *pos; syspath = bolt_device_get_syspath (dev); if (syspath == NULL) return NULL; path = g_strdup (syspath); start = path + strlen ("/sys"); pos = strrchr (start, '/'); if (!pos || pos < start + 2) return NULL; *pos = '\0'; return manager_find_device_by_syspath (mgr, path); } static GPtrArray * bolt_manager_get_children (BoltManager *mgr, BoltDevice *target) { GPtrArray *res; res = g_ptr_array_new_with_free_func (g_object_unref); for (guint i = 0; i < mgr->devices->len; i++) { g_autoptr(BoltDevice) parent = NULL; BoltDevice *dev = g_ptr_array_index (mgr->devices, i); parent = bolt_manager_get_parent (mgr, dev); if (parent != target) continue; g_ptr_array_add (res, g_object_ref (dev)); } return res; } static void bolt_manager_label_device (BoltManager *mgr, BoltDevice *target) { g_autofree char *label = NULL; const char *name; const char *vendor; guint count = 0; static struct { const char *from; const char *to; } vendors[] = { {"Dell Inc.", "Dell" }, {"HP Inc.", "HP" }, {"Apple, Inc.", "Apple"} }; name = bolt_device_get_name (target); vendor = bolt_device_get_vendor (target); /* we count how many duplicate devices we have */ for (guint i = 0; i < mgr->devices->len; i++) { BoltDevice *dev = g_ptr_array_index (mgr->devices, i); const char *dev_name = bolt_device_get_name (dev); const char *dev_vendor = bolt_device_get_vendor (dev); if (bolt_streq (dev_name, name) && bolt_streq (dev_vendor, vendor)) count++; } /* cleanup name: nicer display names for vendors */ for (guint i = 0; i < G_N_ELEMENTS (vendors); i++) if (bolt_streq (vendor, vendors[i].from)) vendor = vendors[i].to; /* cleanup name: Vendor Vendor Device -> Vendor Device */ if (g_str_has_prefix (name, vendor)) { name += strlen (vendor); while (g_ascii_isspace (*name)) name++; if (*name == '\0') name = bolt_device_get_name (target); } /* we counted the target too, > 1 means duplicates */ if (count > 1 && !bolt_device_is_host (target)) label = g_strdup_printf ("%s %s #%u", vendor, name, count); else label = g_strdup_printf ("%s %s", vendor, name); bolt_info (LOG_DEV (target), "labeling device: %s", label); g_object_set (G_OBJECT (target), "label", label, NULL); } /* device authorization */ static void auto_auth_done (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) err = NULL; BoltDevice *dev = BOLT_DEVICE (source); BoltAuth *auth = BOLT_AUTH (res); gboolean ok; ok = bolt_auth_check (auth, &err); if (!ok) bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("auto-auth"), "authorization failed"); else bolt_msg (LOG_DEV (dev), LOG_TOPIC ("auto-auth"), "authorization successful"); } static void manager_auto_authorize (BoltManager *mgr, BoltDevice *dev) { g_autoptr(BoltAuth) auth = NULL; g_autoptr(BoltKey) key = NULL; g_autofree char *amstr = NULL; BoltStatus status; BoltPolicy policy; gboolean authmode; gboolean authorize; gboolean iommu; BoltSecurity level; gboolean ok; status = bolt_device_get_status (dev); policy = bolt_device_get_policy (dev); /* more of a sanity check, because we should only end up * here if the device is not yet authorized and stored */ if (bolt_status_is_authorized (status) || !bolt_device_get_stored (dev)) return; /* The default is not to authorize anything */ authorize = FALSE; /* The following sections are security critical and thus * intentionally verbose and explicit */ /* 1) global auth mode and policy check */ authmode = bolt_auth_mode_is_enabled (mgr->authmode); amstr = bolt_auth_mode_to_string (authmode); iommu = bolt_device_has_iommu (dev); level = bolt_device_get_security (dev); if (authmode) { if (policy == BOLT_POLICY_AUTO) authorize = TRUE; else if (policy == BOLT_POLICY_IOMMU && iommu) authorize = TRUE; } bolt_msg (LOG_DEV (dev), LOG_TOPIC ("auto-auth"), "authmode: %s, policy: %s, iommu: %s -> %s", amstr, bolt_policy_to_string (policy), bolt_yesno (iommu), bolt_okfail (authorize)); if (!authorize) return; /* 2) security level and key check: if we are in SECURE * mode but don't have a key, we DON'T authorize */ if (level == BOLT_SECURITY_SECURE) { g_autoptr(GError) err = NULL; ok = bolt_device_load_key (dev, &key, &err); if (!ok) bolt_warn_err (err, LOG_DEV (dev), "could not load key"); authorize = (key != NULL); } bolt_msg (LOG_DEV (dev), LOG_TOPIC ("auto-auth"), "security: %s mode, key: %s -> %s", bolt_security_for_display (level, iommu), bolt_yesno (key), bolt_okfail (authorize)); if (!authorize) return; auth = bolt_auth_new (mgr, level, key); bolt_device_authorize_idle (dev, auth, auto_auth_done, mgr); } static void manager_do_import_device (BoltManager *mgr, BoltDevice *dev, BoltPolicy policy) { g_autoptr(GError) err = NULL; gboolean ok; ok = bolt_store_put_device (mgr->store, dev, policy, NULL, &err); if (!ok) bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("import"), "failed to store device"); } static void manager_maybe_import (BoltManager *mgr, BoltDevice *dev) { BoltSecurity level; BoltPolicy policy; const char *secstr; const char *polstr; gboolean sl0, sl1; gboolean boot, pcie; gboolean import; gboolean iommu; /* This function is intentionally more verbose then it * could be, but since it is security critical, it is * better to be clear than concise */ g_return_if_fail (!bolt_device_get_stored (dev)); g_return_if_fail (bolt_device_is_authorized (dev)); if (bolt_device_is_host (dev)) { BoltDomain *dom = bolt_device_get_domain (dev); /* host devices are per design authorized and * must therefore never be authorized by bolt */ policy = BOLT_POLICY_MANUAL; /* Store the host device only if its domain is * stored as well. Currently, the only reason * for a domain to not be stored is that its * uuid is not stable, i.e. changes every boot. * But its uuid is in fact derived from the * associated host device, i.e. this very host * device here. Ergo, its uuid is unstable and * thus it should not be stored */ if (bolt_domain_is_stored (dom)) manager_do_import_device (mgr, dev, policy); return; } level = bolt_device_get_security (dev); iommu = bolt_device_has_iommu (dev); boot = bolt_device_check_authflag (dev, BOLT_AUTH_BOOT); pcie = bolt_security_allows_pcie (level); sl0 = level == BOLT_SECURITY_NONE; sl1 = level == BOLT_SECURITY_USER; /* Check if we want to import that device at all, * the fundamental rule is: if it was authorized * by the firmware and we have pcie tunnels we want * to import it so we have a record of it */ import = pcie && (boot || sl0); if (import && !iommu && sl1) policy = BOLT_POLICY_AUTO; else policy = BOLT_POLICY_IOMMU; secstr = bolt_security_for_display (level, iommu); polstr = bolt_policy_to_string (policy); bolt_msg (LOG_DEV (dev), LOG_TOPIC ("import"), "%s mode, boot: %s -> %s", secstr, bolt_yesno (boot), (import ? polstr : "no import")); if (import) manager_do_import_device (mgr, dev, policy); } static void auto_enroll_done (GObject *device, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) err = NULL; BoltDevice *dev = BOLT_DEVICE (device); BoltAuth *auth = BOLT_AUTH (res); BoltManager *mgr; BoltPolicy policy; BoltKey *key; gboolean ok; mgr = BOLT_MANAGER (bolt_auth_get_origin (auth)); ok = bolt_auth_check (auth, &err); if (!ok) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("auto-enroll"), "failed to authorize the new device"); return; } key = bolt_auth_get_key (auth); policy = bolt_auth_get_policy (auth); ok = bolt_store_put_device (mgr->store, dev, policy, key, &err); if (ok) bolt_msg (LOG_DEV (dev), LOG_TOPIC ("auto-enroll"), "done"); else bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("auto-enroll"), "failed to store the device"); } static BoltAuth * manager_enroll_device_prepare (BoltManager *mgr, BoltDevice *dev, GError **error) { g_autoptr(BoltDevice) parent = NULL; g_autoptr(BoltKey) key = NULL; BoltSecurity level; BoltAuth *auth; /* are we even allowed to enroll new devices right now? */ if (bolt_auth_mode_is_disabled (mgr->authmode)) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "authorization of new devices is disabled"); return NULL; } /* if the parent is not authorized and we try to authorize, * we will fail and log a warning, so lets check, fail early * and avoid the warning */ parent = bolt_manager_get_parent (mgr, dev); if (!bolt_device_is_authorized (parent)) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_AUTHCHAIN, "parent not authorized, deferring"); return NULL; } /* determine the maximum security level we can use, i.e. * minimum level that the device *and* host support */ if (bolt_device_supports_secure_mode (dev)) level = bolt_device_get_security (dev); else level = BOLT_SECURITY_USER; /* yay, we are in SECURE mode, we need to create a key */ if (level == BOLT_SECURITY_SECURE) { key = bolt_key_new (error); if (key == NULL) return NULL; } auth = bolt_auth_new (mgr, level, key); /* bolt_auth_new is a wrapper around g_object_new and * therefore cannot realistically fail. This assertion * convinces clang's static analysis of that fact. */ g_assert (auth != NULL); return auth; } static void manager_auto_enroll (BoltManager *mgr, BoltDevice *dev) { g_autoptr(BoltAuth) auth = NULL; g_autoptr(GError) err = NULL; BoltStatus status; gboolean have_key; g_return_if_fail (!bolt_device_get_stored (dev)); /* sanity check for the state */ status = bolt_device_get_status (dev); if (!bolt_status_is_pending (status)) return; /* sanity check for iommu */ if (!bolt_device_has_iommu (dev)) return; auth = manager_enroll_device_prepare (mgr, dev, &err); if (!auth) { bolt_msg (LOG_DEV (dev), LOG_TOPIC ("auto-enroll"), "no, pre-check failed: %s", err->message); return; } have_key = bolt_auth_has_key (auth); bolt_msg (LOG_DEV (dev), LOG_TOPIC ("auto-enroll"), "yes, key: %s", bolt_yesno (have_key)); bolt_auth_set_policy (auth, BOLT_POLICY_IOMMU); bolt_device_authorize (dev, auth, auto_enroll_done, mgr); } /* udev callbacks */ static void handle_uevent_udev (BoltUdev *udev, const char *action, struct udev_device *device, gpointer user_data) { BoltManager *mgr; const char *subsystem; const char *devtype; const char *syspath; mgr = BOLT_MANAGER (user_data); devtype = udev_device_get_devtype (device); subsystem = udev_device_get_subsystem (device); syspath = udev_device_get_syspath (device); if (g_str_equal (action, "add")) manager_probing_device_added (mgr, device); else if (g_str_equal (action, "remove")) manager_probing_device_removed (mgr, device); /* beyond this point only udev device from the * thunderbolt are handled */ if (!bolt_streq (subsystem, "thunderbolt")) return; bolt_debug (LOG_TOPIC ("udev"), "%s (%s%s%s) %s", action, subsystem, devtype ? "/" : "", devtype ? : "", syspath); if (bolt_streq (devtype, "thunderbolt_device")) handle_udev_device_event (mgr, device, action); else if (bolt_streq (devtype, "thunderbolt_domain")) handle_udev_domain_event (mgr, device, action); } static void handle_udev_domain_event (BoltManager *mgr, struct udev_device *device, const char *action) { const char *syspath; BoltDomain *domain; syspath = udev_device_get_syspath (device); if (g_str_equal (action, "add")) { manager_probing_domain_added (mgr, device); /* the creation of the actual domain object and * its registration is handled on-demand: only * when the host device appears, the uevent * handler for the thunderbolt devices will * ensure the domain exists */ } else if (g_str_equal (action, "change")) { domain = manager_find_domain_by_syspath (mgr, syspath); if (domain == NULL) { bolt_warn (LOG_TOPIC ("domain"), "unregistered domain changed at %s", syspath); return; } bolt_domain_update_from_udev (domain, device); } else if (g_str_equal (action, "remove")) { domain = manager_find_domain_by_syspath (mgr, syspath); if (!domain) { bolt_warn (LOG_TOPIC ("domain"), "unregistered domain removed at %s", syspath); return; } if (bolt_domain_is_stored (domain)) bolt_domain_disconnected (domain); else handle_udev_domain_removed (mgr, domain); } } static void handle_udev_domain_removed (BoltManager *mgr, BoltDomain *domain) { const char *name; name = bolt_domain_get_id (domain); bolt_info (LOG_TOPIC ("domain"), "'%s' removed", name); if (bolt_exported_is_exported (BOLT_EXPORTED (domain))) { const char *op; gboolean ok; op = bolt_exported_get_object_path (BOLT_EXPORTED (domain)); bolt_exported_emit_signal (BOLT_EXPORTED (mgr), "DomainRemoved", g_variant_new ("(o)", op), NULL); ok = bolt_exported_unexport (BOLT_EXPORTED (domain)); bolt_info (LOG_TOPIC ("dbus"), "%s unexported: %s", name, bolt_okfail (ok)); } manager_deregister_domain (mgr, domain); } static void handle_udev_device_event (BoltManager *mgr, struct udev_device *device, const char *action) { g_autoptr(BoltDevice) dev = NULL; const char *syspath; syspath = udev_device_get_syspath (device); if (g_str_equal (action, "add") || g_str_equal (action, "change")) { BoltDomain *dom; const char *uid; /* filter sysfs devices (e.g. the domain) that don't have * the unique_id attribute */ uid = bolt_sysfs_device_get_unique_id (device, NULL); if (uid == NULL) return; dom = manager_domain_ensure (mgr, device); if (dom == NULL) { bolt_warn (LOG_TOPIC ("domain"), "could not find domain for device at '%s'", syspath); return; } dev = manager_find_device_by_uid (mgr, uid, NULL); if (!dev) handle_udev_device_added (mgr, dom, device); else if (!bolt_device_is_connected (dev)) handle_udev_device_attached (mgr, dom, dev, device); else handle_udev_device_changed (mgr, dev, device); } else if (g_str_equal (action, "remove")) { const char *name; /* filter out the domain controller */ name = udev_device_get_sysname (device); if (name && g_str_has_prefix (name, "domain")) return; dev = manager_find_device_by_syspath (mgr, syspath); /* if we don't have any records of the device, * then we don't care */ if (!dev) return; if (bolt_device_get_stored (dev)) handle_udev_device_detached (mgr, dev); else handle_udev_device_removed (mgr, dev); } } static void handle_udev_device_added (BoltManager *mgr, BoltDomain *domain, struct udev_device *udev) { g_autoptr(GError) err = NULL; GDBusConnection *bus; BoltDevice *dev; BoltStatus status; const char *opath; const char *syspath; syspath = udev_device_get_syspath (udev); dev = bolt_device_new_for_udev (udev, domain, &err); if (dev == NULL) { bolt_warn_err (err, LOG_TOPIC ("udev"), "could not create device for %s", syspath); return; } manager_register_device (mgr, dev); status = bolt_device_get_status (dev); bolt_msg (LOG_DEV (dev), "device added, status: %s, at %s", bolt_status_to_string (status), syspath); bolt_manager_label_device (mgr, dev); if (bolt_status_is_authorized (status)) manager_maybe_import (mgr, dev); else if (bolt_domain_has_iommu (domain)) manager_auto_enroll (mgr, dev); /* if we have a valid dbus connection */ bus = bolt_exported_get_connection (BOLT_EXPORTED (mgr)); if (bus == NULL) return; opath = bolt_device_export (dev, bus, &err); if (opath == NULL) bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("dbus"), "error exporting"); else bolt_info (LOG_DEV (dev), LOG_TOPIC ("dbus"), "exported device at %.43s...", opath); bolt_exported_emit_signal (BOLT_EXPORTED (mgr), "DeviceAdded", g_variant_new ("(o)", opath), NULL); } static void handle_udev_device_changed (BoltManager *mgr, BoltDevice *dev, struct udev_device *udev) { BoltStatus after; BoltStatus before; before = bolt_device_get_status (dev); after = bolt_device_update_from_udev (dev, udev); bolt_info (LOG_DEV (dev), LOG_TOPIC ("udev"), "device changed: %s -> %s", bolt_status_to_string (before), bolt_status_to_string (after)); /* reaction to status changes of the device, i.e. * authorizing the children are done in the * handle_device_status_changed () signal handler */ } static void handle_udev_device_removed (BoltManager *mgr, BoltDevice *dev) { const char *syspath; syspath = bolt_device_get_syspath (dev); bolt_msg (LOG_DEV (dev), "removed (%s)", syspath); manager_deregister_device (mgr, dev); } static void handle_udev_device_attached (BoltManager *mgr, BoltDomain *domain, BoltDevice *dev, struct udev_device *udev) { g_autoptr(BoltDevice) parent = NULL; const char *syspath; BoltStatus status; syspath = udev_device_get_syspath (udev); status = bolt_device_connected (dev, domain, udev); bolt_msg (LOG_DEV (dev), "connected: %s (%s)", bolt_status_to_string (status), syspath); if (status != BOLT_STATUS_CONNECTED) return; parent = bolt_manager_get_parent (mgr, dev); if (parent) { const char *pid = bolt_device_get_uid (parent); status = bolt_device_get_status (parent); if (!bolt_status_is_authorized (status)) { bolt_info (LOG_DEV (dev), "parent [%s] not authorized", pid); return; } } else { bolt_warn (LOG_DEV (dev), "could not find parent"); } manager_auto_authorize (mgr, dev); } static void handle_udev_device_detached (BoltManager *mgr, BoltDevice *dev) { const char *syspath; syspath = bolt_device_get_syspath (dev); bolt_msg (LOG_DEV (dev), "disconnected (%s)", syspath); bolt_device_disconnected (dev); } static void handle_store_device_added (BoltStore *store, const char *uid, BoltManager *mgr) { g_autoptr(BoltDevice) dev = NULL; BoltDomain *dom = mgr->domains; dev = manager_find_device_by_uid (mgr, uid, NULL); if (dev == NULL || dom == NULL) return; if (bolt_device_get_policy (dev) != BOLT_POLICY_AUTO) { bolt_info (LOG_TOPIC ("bootacl"), LOG_DEV_UID (uid), "policy not 'auto', not adding"); return; } bolt_domain_foreach (dom, bolt_bootacl_add, dev); } static void handle_store_device_removed (BoltStore *store, const char *uid, BoltManager *mgr) { g_autoptr(BoltDevice) dev = NULL; BoltDomain *dom = mgr->domains; BoltStatus status; dev = manager_find_device_by_uid (mgr, uid, NULL); if (!dev) return; bolt_msg (LOG_DEV (dev), "removed from store"); /* TODO: maybe move to a new bolt_device_removed (dev) */ g_object_set (dev, "store", NULL, "key", BOLT_KEY_MISSING, "policy", BOLT_POLICY_DEFAULT, NULL); /* remove device from bootacl */ bolt_domain_foreach (dom, bolt_bootacl_del, dev); status = bolt_device_get_status (dev); /* if the device is connected, keep it around */ if (status != BOLT_STATUS_DISCONNECTED) return; manager_deregister_device (mgr, dev); } static void handle_domain_security_changed (BoltManager *mgr, GParamSpec *unused, BoltDomain *domain) { BoltSecurity security = bolt_domain_get_security (domain); gboolean online = bolt_domain_is_connected (domain); if (online) manager_maybe_set_security (mgr, security); } static void handle_device_status_changed (BoltDevice *dev, BoltStatus old, BoltManager *mgr) { g_autoptr(GPtrArray) children = NULL; BoltStatus now; now = bolt_device_get_status (dev); bolt_debug (LOG_DEV (dev), "status changed: %s -> %s", bolt_status_to_string (old), bolt_status_to_string (now)); if (now == old) return; /* sanity check */ if (now == BOLT_STATUS_AUTHORIZING) mgr->authorizing += 1; else if (old == BOLT_STATUS_AUTHORIZING) mgr->authorizing -= 1; manager_probing_activity (mgr, !mgr->authorizing); if (now != BOLT_STATUS_AUTHORIZED) return; /* see if the new status changes anything for the * children, e.g. the can now be authorized */ children = bolt_manager_get_children (mgr, dev); for (guint i = 0; i < children->len; i++) { BoltDevice *child = g_ptr_array_index (children, i); if (bolt_device_get_stored (child)) manager_auto_authorize (mgr, child); else if (bolt_device_has_iommu (child)) manager_auto_enroll (mgr, child); } } static void handle_device_generation_changed (BoltDevice *dev, GParamSpec *unused, BoltManager *mgr) { guint gen = bolt_device_get_generation (dev); bolt_debug (LOG_DEV (dev), LOG_TOPIC ("generation"), "updated to: %u", gen); if (!bolt_device_is_host (dev)) return; manager_maybe_set_generation (mgr, gen); } static void handle_power_state_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data) { BoltManager *mgr = BOLT_MANAGER (user_data); gboolean supported = bolt_power_can_force (mgr->power); BoltPowerState state = bolt_power_get_state (mgr->power); bolt_info (LOG_TOPIC ("power"), "state changed: %s/%s", (supported ? "supported" : "unsupported"), bolt_power_state_to_string (state)); g_object_notify_by_pspec (G_OBJECT (mgr), props[PROP_POWERSTATE]); manager_sd_notify_status (mgr); } static gboolean probing_timeout (gpointer user_data) { BoltManager *mgr; gint64 now, dt, timeout; mgr = BOLT_MANAGER (user_data); if (mgr->authorizing > 0) return G_SOURCE_CONTINUE; now = g_get_monotonic_time (); dt = now - mgr->probing_tstamp; /* dt is in microseconds, probing timeout in * milli seconds */ timeout = mgr->probing_tsettle * BOLT_USEC_PER_MSEC; if (dt < timeout) return G_SOURCE_CONTINUE; /* we are done, remove us */ mgr->probing_timeout = 0; g_object_notify_by_pspec (G_OBJECT (mgr), props[PROP_PROBING]); bolt_info (LOG_TOPIC ("probing"), "timeout, done: [%ld] (%ld)", dt, timeout); return G_SOURCE_REMOVE; } static void manager_probing_activity (BoltManager *mgr, gboolean weak) { guint dt; mgr->probing_tstamp = g_get_monotonic_time (); if (mgr->probing_timeout || weak) return; dt = mgr->probing_tsettle / 2; bolt_info (LOG_TOPIC ("probing"), "started [%u]", dt); mgr->probing_timeout = g_timeout_add (dt, probing_timeout, mgr); g_object_notify_by_pspec (G_OBJECT (mgr), props[PROP_PROBING]); } static gboolean device_is_thunderbolt_root (struct udev_device *dev) { const char *driver; const char *subsys; driver = udev_device_get_driver (dev); subsys = udev_device_get_subsystem (dev); return bolt_streq (subsys, "pci") && bolt_streq (driver, "thunderbolt"); } static gboolean device_is_wakeup (struct udev_device *dev) { const char *subsys; subsys = udev_device_get_subsystem (dev); return bolt_streq (subsys, "wakeup"); } static gboolean probing_add_root (BoltManager *mgr, struct udev_device *dev) { const char *syspath; GPtrArray *roots; g_return_val_if_fail (device_is_thunderbolt_root (dev), FALSE); /* we go two levels up */ for (guint i = 0; dev != NULL && i < 2; i++) dev = udev_device_get_parent (dev); if (dev == NULL) return FALSE; roots = mgr->probing_roots; syspath = udev_device_get_syspath (dev); g_ptr_array_add (roots, g_strdup (syspath)); bolt_info (LOG_TOPIC ("probing"), "adding %s to roots", syspath); return TRUE; } static void manager_probing_device_added (BoltManager *mgr, struct udev_device *dev) { const char *syspath; GPtrArray *roots; gboolean added; syspath = udev_device_get_syspath (dev); if (syspath == NULL) return; /* ignore events for wakeup devices which get removed * and added at random time without any connection to * any physical thunderbolt device */ if (device_is_wakeup (dev)) return; roots = mgr->probing_roots; for (guint i = 0; i < roots->len; i++) { const char *r = g_ptr_array_index (roots, i); if (g_str_has_prefix (syspath, r)) { bolt_debug (LOG_TOPIC ("probing"), "match %s", syspath); /* do something */ manager_probing_activity (mgr, FALSE); return; } } /* if we ended up here we didn't find a root, * maybe we are one */ if (!device_is_thunderbolt_root (dev)) return; added = probing_add_root (mgr, dev); if (added) manager_probing_activity (mgr, FALSE); } static void manager_probing_device_removed (BoltManager *mgr, struct udev_device *dev) { const char *syspath; GPtrArray *roots; gboolean found; guint index; syspath = udev_device_get_syspath (dev); if (syspath == NULL) return; roots = mgr->probing_roots; found = FALSE; for (index = 0; index < roots->len; index++) { const char *r = g_ptr_array_index (roots, index); found = g_str_equal (syspath, r); if (found) break; } if (!found) return; bolt_info (LOG_TOPIC ("probing"), "removing %s from roots", syspath); g_ptr_array_remove_index_fast (mgr->probing_roots, index); } static void manager_probing_domain_added (BoltManager *mgr, struct udev_device *domain) { struct udev_device *p = domain; /* walk up until we find the thunderbolt root */ while (p && !device_is_thunderbolt_root (p)) p = udev_device_get_parent (p); if (p == NULL) return; probing_add_root (mgr, p); } static BoltGuard * manager_maybe_power_controller (BoltManager *mgr) { g_autoptr(GError) err = NULL; BoltGuard *guard = NULL; gboolean can_force_power; int n; can_force_power = bolt_power_can_force (mgr->power); if (can_force_power == FALSE) return NULL; n = bolt_udev_count_hosts (mgr->udev, &err); if (n < 0) { bolt_warn_err (err, LOG_TOPIC ("udev"), "failed to count domains"); return NULL; } else if (n > 0) { goto out; } guard = bolt_power_acquire (mgr->power, &err); if (guard == NULL) { bolt_warn_err (err, LOG_TOPIC ("power"), "could not force power"); return NULL; } /* we wait for a total of 5.0 seconds, should hopefully * be enough for at least the domain to show up. */ for (int i = 0; i < 25 && n < 1; i++) { g_usleep (200000); /* 200 000 us = 0.2s */ n = bolt_udev_count_hosts (mgr->udev, NULL); } out: bolt_info (LOG_TOPIC ("udev"), "found %d domain%s", n, n > 1 ? "s" : ""); return guard; } /* config */ static void manager_load_user_config (BoltManager *mgr) { g_autoptr(GError) err = NULL; BoltPolicy policy; BoltAuthMode authmode; BoltTri res; bolt_info (LOG_TOPIC ("config"), "loading user config"); mgr->config = bolt_store_config_load (mgr->store, &err); if (mgr->config == NULL) { if (!bolt_err_notfound (err)) bolt_warn_err (err, LOG_TOPIC ("config"), "failed to load user config"); return; } bolt_info (LOG_TOPIC ("config"), "user config loaded successfully"); res = bolt_config_load_default_policy (mgr->config, &policy, &err); if (res == TRI_ERROR) { bolt_warn_err (err, LOG_TOPIC ("config"), "failed to load default policy"); g_clear_error (&err); } else if (res == TRI_YES) { mgr->policy = policy; bolt_info (LOG_TOPIC ("config"), "default policy set to %s", bolt_policy_to_string (policy)); g_object_notify_by_pspec (G_OBJECT (mgr), props[PROP_POLICY]); } res = bolt_config_load_auth_mode (mgr->config, &authmode, &err); if (res == TRI_ERROR) { bolt_warn_err (err, LOG_TOPIC ("config"), "failed to load auth mode"); g_clear_error (&err); } else if (res == TRI_YES) { g_autofree char *str = NULL; str = bolt_flags_to_string (BOLT_TYPE_AUTH_MODE, authmode, NULL); bolt_info (LOG_TOPIC ("config"), "auth mode set to '%s'", str); mgr->authmode = authmode; g_object_notify_by_pspec (G_OBJECT (mgr), props[PROP_POLICY]); } } /* dbus property setter */ static gboolean handle_set_authmode (BoltExported *obj, const char *name, const GValue *value, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *str = NULL; BoltManager *mgr = BOLT_MANAGER (obj); BoltAuthMode authmode; gboolean ok; authmode = g_value_get_flags (value); if (authmode == mgr->authmode) return TRUE; if (mgr->config == NULL) mgr->config = bolt_config_user_init (); str = bolt_flags_to_string (BOLT_TYPE_AUTH_MODE, authmode, &err); if (str == NULL) { bolt_warn_err (err, LOG_TOPIC ("config"), "error setting authmode"); return bolt_error_propagate (error, &err); } bolt_config_set_auth_mode (mgr->config, str); ok = bolt_store_config_save (mgr->store, mgr->config, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("config"), "error saving config"); return bolt_error_propagate (error, &err); } mgr->authmode = authmode; bolt_info (LOG_TOPIC ("config"), "auth mode set to '%s'", str); manager_sd_notify_status (mgr); return ok; } /* dbus methods: domain related */ static GVariant * handle_list_domains (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error) { BoltManager *mgr = BOLT_MANAGER (object); const char **domains; BoltDomain *iter; guint count; count = bolt_domain_count (mgr->domains); domains = g_newa (const char *, count + 1); iter = mgr->domains; for (guint i = 0; i < count; i++) { domains[i] = bolt_exported_get_object_path (BOLT_EXPORTED (iter)); iter = bolt_domain_next (iter); } domains[count] = NULL; return g_variant_new ("(^ao)", domains); } static GVariant * handle_domain_by_id (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error) { BoltManager *mgr = BOLT_MANAGER (object); BoltDomain *domain; const char *op; const char *id; g_variant_get (params, "(&s)", &id); if (id == NULL || id[0] == '\0') { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "empty domain id"); return NULL; } domain = bolt_domain_find_id (mgr->domains, id, error); if (domain == NULL) return NULL; op = bolt_exported_get_object_path (BOLT_EXPORTED (domain)); return g_variant_new ("(o)", op); } /* dbus methods: device related */ static GVariant * handle_list_devices (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error) { BoltManager *mgr = BOLT_MANAGER (obj); const char **devs; devs = g_newa (const char *, mgr->devices->len + 1); for (guint i = 0; i < mgr->devices->len; i++) { BoltDevice *d = g_ptr_array_index (mgr->devices, i); devs[i] = bolt_device_get_object_path (d); } devs[mgr->devices->len] = NULL; return g_variant_new ("(^ao)", devs); } static GVariant * handle_device_by_uid (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error) { g_autoptr(BoltDevice) dev = NULL; BoltManager *mgr; const char *uid; const char *opath; mgr = BOLT_MANAGER (obj); g_variant_get (params, "(&s)", &uid); dev = manager_find_device_by_uid (mgr, uid, error); if (dev == NULL) return NULL; opath = bolt_device_get_object_path (dev); return g_variant_new ("(o)", opath); } static void enroll_device_done (GObject *device, GAsyncResult *res, gpointer user_data) { GDBusMethodInvocation *inv = user_data; BoltDevice *dev = BOLT_DEVICE (device); BoltAuth *auth = BOLT_AUTH (res); GError *error = NULL; BoltManager *mgr; const char *opath; gboolean ok; mgr = BOLT_MANAGER (bolt_auth_get_origin (auth)); ok = bolt_auth_check (auth, &error); if (ok) { ok = bolt_store_put_device (mgr->store, dev, bolt_auth_get_policy (auth), bolt_auth_get_key (auth), &error); } if (!ok) { g_dbus_method_invocation_take_error (inv, error); return; } opath = bolt_device_get_object_path (dev); g_dbus_method_invocation_return_value (inv, g_variant_new ("(o)", opath)); } static GVariant * enroll_device_store_authorized (BoltManager *mgr, BoltDevice *dev, BoltPolicy policy, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(BoltKey) key = NULL; const char *opath; gboolean ok; bolt_info (LOG_DEV (dev), "enrolling an authorized device (%s)", bolt_policy_to_string (policy)); ok = bolt_device_get_key_from_sysfs (dev, &key, &err); if (!ok) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("udev"), "failed to read key from sysfs"); g_propagate_prefixed_error (error, g_steal_pointer (&err), "%s", "could not determine existing authorization: "); return NULL; } ok = bolt_store_put_device (mgr->store, dev, policy, key, &err); if (!ok) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("store"), "failed to store device"); bolt_error_propagate (error, &err); return NULL; } opath = bolt_device_get_object_path (dev); return g_variant_new ("(o)", opath); } static GVariant * handle_enroll_device (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error) { g_autoptr(BoltDevice) dev = NULL; g_autoptr(BoltAuth) auth = NULL; BoltManager *mgr; const char *uid; BoltPolicy pol; const char *policy; mgr = BOLT_MANAGER (obj); g_variant_get_child (params, 0, "&s", &uid); g_variant_get_child (params, 1, "&s", &policy); dev = manager_find_device_by_uid (mgr, uid, error); if (dev == NULL) return NULL; pol = bolt_enum_from_string (BOLT_TYPE_POLICY, policy, error); if (pol == BOLT_POLICY_UNKNOWN) { if (*error == NULL) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "invalid policy: %s", policy); return NULL; } else if (pol == BOLT_POLICY_DEFAULT) { if (bolt_device_has_iommu (dev)) pol = BOLT_POLICY_IOMMU; else pol = mgr->policy; bolt_info (LOG_DEV (dev), LOG_TOPIC ("enroll"), "got 'default' policy, adjusted to: '%s'", bolt_policy_to_string (pol)); } if (bolt_device_get_stored (dev)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS, "device with id '%s' already enrolled.", uid); return NULL; } /* if the device is already authorized, we just store it */ if (bolt_device_is_authorized (dev)) return enroll_device_store_authorized (mgr, dev, pol, error); auth = manager_enroll_device_prepare (mgr, dev, error); if (auth == NULL) return NULL; bolt_auth_set_policy (auth, pol); bolt_device_authorize (dev, auth, enroll_device_done, inv); return NULL; } static GVariant * handle_forget_device (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error) { g_autoptr(BoltDevice) dev = NULL; BoltManager *mgr; gboolean ok; const char *uid; mgr = BOLT_MANAGER (obj); g_variant_get (params, "(&s)", &uid); dev = manager_find_device_by_uid (mgr, uid, error); if (dev == NULL) return FALSE; ok = bolt_store_del (mgr->store, dev, error); return ok ? g_variant_new ("()") : NULL; } /* public methods */ gboolean bolt_manager_export (BoltManager *mgr, GDBusConnection *connection, GError **error) { g_autoptr(GError) err = NULL; gboolean ok; if (!bolt_exported_export (BOLT_EXPORTED (mgr), connection, BOLT_DBUS_PATH, error)) return FALSE; ok = bolt_exported_export (BOLT_EXPORTED (mgr->power), connection, BOLT_DBUS_PATH, error); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("dbus"), "failed to export power object"); g_clear_error (&err); } bolt_domain_foreach (mgr->domains, (GFunc) bolt_domain_export, connection); for (guint i = 0; i < mgr->devices->len; i++) { BoltDevice *dev = g_ptr_array_index (mgr->devices, i); const char *opath; opath = bolt_device_export (dev, connection, &err); if (opath == NULL) { bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("dbus"), "error exporting a device"); g_clear_error (&err); continue; } bolt_info (LOG_DEV (dev), LOG_TOPIC ("dbus"), "exported device at %.43s...", opath); } return TRUE; } void bolt_manager_got_the_name (BoltManager *mgr) { /* emit DeviceAdded signals now that we have the name * for all devices that are not stored and connected */ for (guint i = 0; i < mgr->devices->len; i++) { BoltDevice *dev = g_ptr_array_index (mgr->devices, i); BoltStatus status; gboolean stored; const char *opath; stored = bolt_device_get_stored (dev); if (stored) continue; status = bolt_device_get_status (dev); if (status != BOLT_STATUS_CONNECTED) continue; opath = bolt_exported_get_object_path (BOLT_EXPORTED (dev)); if (opath == NULL) continue; bolt_exported_emit_signal (BOLT_EXPORTED (mgr), "DeviceAdded", g_variant_new ("(o)", opath), NULL); } } bolt-0.9.2/boltd/bolt-manager.h000066400000000000000000000023241417453051000163100ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-exported.h" G_BEGIN_DECLS #define BOLT_TYPE_MANAGER bolt_manager_get_type () G_DECLARE_FINAL_TYPE (BoltManager, bolt_manager, BOLT, MANAGER, BoltExported); gboolean bolt_manager_export (BoltManager *mgr, GDBusConnection *connection, GError **error); void bolt_manager_got_the_name (BoltManager *mgr); G_END_DECLS bolt-0.9.2/boltd/bolt-power.c000066400000000000000000000615511417453051000160340ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-power.h" #include "bolt-config.h" #include "bolt-dbus.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-log.h" #include "bolt-io.h" #include "bolt-reaper.h" #include "bolt-str.h" #include "bolt-unix.h" #include #include #include #include #define POWER_WAIT_TIMEOUT 20 * 1000 // 20 seconds #define POWER_REAPER_TIMEOUT 20 // seconds #define DEFAULT_STATEDIR "power" #define STATE_FILENAME "on" /* prototypes */ static void bolt_power_release (BoltPower *power, BoltGuard *guard); /* GObject */ static void power_initable_iface_init (GInitableIface *iface); static gboolean bolt_power_initialize (GInitable *initable, GCancellable *cancellable, GError **error); /* utilities */ static char * bolt_power_gen_guard_id (BoltPower *power, GError **error); static gboolean bolt_power_recover_guards (BoltPower *power, GError **error); static void bolt_power_timeout_reset (BoltPower *power); static gboolean bolt_power_switch_toggle (BoltPower *power, gboolean on, GError **error); /* callbacks and signals */ static gboolean bolt_power_wait_timeout (gpointer user_data); static void handle_uevent_udev (BoltUdev *udev, const char *action, struct udev_device *device, gpointer user_data); /* dbus methods */ static GVariant * handle_list_guards (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); static GVariant * handle_force_power (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error); struct _BoltPower { BoltExported object; /* path to store run time data */ char *runpath; GFile *statedir; GFile *statefile; /* connection to udev */ BoltUdev *udev; /* the path to the sysfs device file, * or NULL if force power is unavailable */ char *path; BoltPowerState state; /* */ guint16 guard_num; GHashTable *guards; /* wait before off handling */ guint wait_id; guint timeout; /* milliseconds */ }; enum { PROP_0, PROP_RUNDIR, PROP_STATEDIR, PROP_UDEV, PROP_SUPPORTED, PROP_STATE, PROP_TIMEOUT, PROP_LAST }; static GParamSpec *power_props[PROP_LAST] = { NULL, }; G_DEFINE_TYPE_WITH_CODE (BoltPower, bolt_power, BOLT_TYPE_EXPORTED, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, power_initable_iface_init)); static void bolt_power_finalize (GObject *object) { BoltPower *power = BOLT_POWER (object); if (power->wait_id != 0) { g_source_remove (power->wait_id); bolt_power_wait_timeout (power); } g_clear_pointer (&power->runpath, g_free); g_clear_object (&power->statedir); g_clear_object (&power->statefile); g_clear_object (&power->udev); g_clear_pointer (&power->path, g_free); g_clear_pointer (&power->guards, g_hash_table_unref); G_OBJECT_CLASS (bolt_power_parent_class)->finalize (object); } static void bolt_power_init (BoltPower *power) { power->state = BOLT_FORCE_POWER_UNSET; power->guards = g_hash_table_new (g_str_hash, g_str_equal); } static void bolt_power_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltPower *power = BOLT_POWER (object); switch (prop_id) { case PROP_RUNDIR: g_value_set_string (value, power->runpath); break; case PROP_STATEDIR: g_value_set_object (value, power->statedir); break; case PROP_UDEV: g_value_set_object (value, power->udev); break; case PROP_SUPPORTED: g_value_set_boolean (value, power->path != NULL); break; case PROP_STATE: g_value_set_enum (value, power->state); break; case PROP_TIMEOUT: g_value_set_uint (value, power->timeout); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_power_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltPower *power = BOLT_POWER (object); switch (prop_id) { case PROP_RUNDIR: power->runpath = g_value_dup_string (value); break; case PROP_UDEV: power->udev = g_value_dup_object (value); break; case PROP_TIMEOUT: power->timeout = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_power_constructed (GObject *obj) { BoltPower *power = BOLT_POWER (obj); G_OBJECT_CLASS (bolt_power_parent_class)->constructed (obj); if (power->runpath == NULL) power->runpath = g_strdup (bolt_get_runtime_directory ()); } static void bolt_power_class_init (BoltPowerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); BoltExportedClass *exported_class = BOLT_EXPORTED_CLASS (klass); gobject_class->finalize = bolt_power_finalize; gobject_class->constructed = bolt_power_constructed; gobject_class->get_property = bolt_power_get_property; gobject_class->set_property = bolt_power_set_property; power_props[PROP_RUNDIR] = g_param_spec_string ("rundir", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); power_props[PROP_STATEDIR] = g_param_spec_object ("statedir", NULL, NULL, G_TYPE_FILE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); power_props[PROP_UDEV] = g_param_spec_object ("udev", NULL, NULL, BOLT_TYPE_UDEV, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); power_props[PROP_SUPPORTED] = g_param_spec_boolean ("supported", "Supported", NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_NICK); power_props[PROP_STATE] = g_param_spec_enum ("state", "State", NULL, BOLT_TYPE_POWER_STATE, BOLT_FORCE_POWER_UNSET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); power_props[PROP_TIMEOUT] = g_param_spec_uint ("timeout", "Timeout", NULL, 0, G_MAXINT, POWER_WAIT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, power_props); bolt_exported_class_set_interface_info (exported_class, BOLT_DBUS_POWER_INTERFACE, BOLT_DBUS_GRESOURCE_PATH); bolt_exported_class_export_properties (exported_class, PROP_SUPPORTED, PROP_LAST, power_props); bolt_exported_class_export_method (exported_class, "ForcePower", handle_force_power); bolt_exported_class_export_method (exported_class, "ListGuards", handle_list_guards); } static void power_initable_iface_init (GInitableIface *iface) { iface->init = bolt_power_initialize; } static gboolean bolt_power_initialize (GInitable *initable, GCancellable *cancellable, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(BoltGuard) guard = NULL; g_autofree char *statedir = NULL; BoltPower *power = BOLT_POWER (initable); gboolean on = FALSE; gboolean ok; guint guards; statedir = g_build_filename (power->runpath, DEFAULT_STATEDIR, NULL); power->statedir = g_file_new_for_path (statedir); power->statefile = g_file_get_child (power->statedir, STATE_FILENAME); bolt_info (LOG_TOPIC ("power"), "state located at: %s", statedir); ok = g_file_make_directory_with_parents (power->statedir, NULL, &err); if (!ok && !bolt_err_exists (err)) bolt_warn_err (err, LOG_TOPIC ("power"), "failed to create guarddir at %s", statedir); g_clear_error (&err); g_signal_connect_object (power->udev, "uevent", (GCallback) handle_uevent_udev, power, 0); ok = bolt_udev_detect_force_power (power->udev, &power->path, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("power"), "failure while detecting force power"); g_clear_error (&err); } bolt_msg (LOG_TOPIC ("power"), "force power support: %s", bolt_yesno (power->path != NULL)); if (power->path == NULL) return TRUE; /* recover force power state */ on = g_file_query_exists (power->statefile, NULL); /* recover saved power guards */ ok = bolt_power_recover_guards (power, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("power"), "failed to recover guards"); g_clear_error (&err); /* NOT a critical failure */ } /* enforce that our ON state is actually true */ guards = g_hash_table_size (power->guards); if (on || guards > 0) { bolt_msg (LOG_TOPIC ("power"), "recovered state, on: %s, guards: %u", bolt_yesno (on), guards); bolt_info (LOG_TOPIC ("power"), "creating temporary power guard"); guard = bolt_power_acquire (power, &err); if (guard == NULL) bolt_warn_err (err, LOG_TOPIC ("power"), "failed to force-power controller"); /* failures here are not critical */ } return TRUE; } /* internal methods */ static gboolean bolt_power_recover_guards (BoltPower *power, GError **error) { g_autoptr(GPtrArray) guards = NULL; g_autofree char *statedir = NULL; statedir = g_file_get_path (power->statedir); guards = bolt_guard_recover (statedir, error); if (guards == NULL) return FALSE; for (guint i = 0; i < guards->len; i++) { BoltGuard *guard = g_ptr_array_index (guards, i); const char *id = bolt_guard_get_id (guard); const char *who = bolt_guard_get_who (guard); const guint pid = bolt_guard_get_pid (guard); bolt_info (LOG_TOPIC ("power"), "guard '%s' for '%s' (pid %u) recovered", id, who, pid); g_signal_connect_object (guard, "released", (GCallback) bolt_power_release, power, G_CONNECT_SWAPPED); g_hash_table_insert (power->guards, (gpointer) bolt_guard_get_id (guard), (gpointer) g_object_ref (guard)); } return TRUE; } static gboolean bolt_power_wait_timeout (gpointer user_data) { g_autoptr(GError) err = NULL; BoltPower *power = user_data; gboolean ok; if (power->path == NULL) /* force power support got removed while being used, * this was already complained about, so ignore it */ return G_SOURCE_REMOVE; /* we just removed the last active guard */ ok = bolt_power_switch_toggle (power, FALSE, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("power"), "failed to turn off force_power"); power->wait_id = 0; return G_SOURCE_REMOVE; } static void handle_uevent_thunderbolt (BoltPower *power, const char *action, struct udev_device *device) { /* no callback scheduled, nothing to do */ if (power->wait_id == 0) return; /* only interested in added devices */ if (!bolt_streq (action, "add")) return; /* if we are not in WAIT state, we don't * do anything, but if we are, we want * to reset the timeout */ if (power->state != BOLT_FORCE_POWER_WAIT) return; bolt_info (LOG_TOPIC ("power"), "resetting timeout (uevent %s)", udev_device_get_syspath (device)); bolt_power_timeout_reset (power); } static void handle_uevent_wmi (BoltPower *power, const char *action, struct udev_device *device) { g_autoptr(GError) err = NULL; g_autofree char *path = NULL; const char *name; const char *syspath; gboolean changed = FALSE; gboolean ok; syspath = udev_device_get_syspath (device); name = udev_device_get_sysname (device); bolt_debug (LOG_TOPIC ("power"), "uevent: wmi %s %s [%s %s]", action, name, syspath, power->path ? : ""); if (!bolt_streq (action, "change")) return; ok = bolt_udev_detect_force_power (power->udev, &path, &err); if (!ok) { bolt_warn_err (err, LOG_TOPIC ("udev"), "failed to detect force_power support"); g_clear_error (&err); } if (path != NULL) { if (power->path == NULL || !bolt_streq (path, power->path)) { bolt_set_str (&power->path, g_steal_pointer (&path)); changed = TRUE; } } else if (path == NULL && power->path != NULL) { if (power->state > BOLT_FORCE_POWER_OFF) bolt_warn (LOG_TOPIC ("power"), "force power supported removed while active"); g_clear_pointer (&power->path, g_free); changed = TRUE; } if (changed) { /* if changed, we don't know our current state */ power->state = BOLT_FORCE_POWER_UNSET; g_object_notify_by_pspec (G_OBJECT (power), power_props[PROP_STATE]); g_object_notify_by_pspec (G_OBJECT (power), power_props[PROP_SUPPORTED]); } } static void handle_uevent_udev (BoltUdev *udev, const char *action, struct udev_device *device, gpointer user_data) { BoltPower *power = BOLT_POWER (user_data); const char *subsystem; subsystem = udev_device_get_subsystem (device); if (bolt_streq (subsystem, "thunderbolt")) handle_uevent_thunderbolt (power, action, device); else if (bolt_streq (subsystem, "wmi")) handle_uevent_wmi (power, action, device); } static void bolt_power_timeout_reset (BoltPower *power) { if (power->wait_id > 0) g_source_remove (power->wait_id); power->wait_id = g_timeout_add (power->timeout, bolt_power_wait_timeout, power); if (power->state != BOLT_FORCE_POWER_WAIT) { power->state = BOLT_FORCE_POWER_WAIT; g_object_notify_by_pspec (G_OBJECT (power), power_props[PROP_STATE]); } } static gboolean bolt_power_switch_toggle (BoltPower *power, gboolean on, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *statepath = NULL; BoltPowerState state; gboolean ok; int fd; g_return_val_if_fail (BOLT_IS_POWER (power), FALSE); g_return_val_if_fail (power->path != NULL, FALSE); bolt_info (LOG_TOPIC ("power"), "setting force_power to %s", on ? "ON" : "OFF"); fd = bolt_open (power->path, O_WRONLY, 0, error); if (fd < 0) return FALSE; ok = bolt_write_all (fd, on ? "1" : "0", 1, error); bolt_close (fd, NULL); if (!ok) return FALSE; statepath = g_file_get_path (power->statefile); if (on) { state = BOLT_FORCE_POWER_ON; fd = bolt_open (statepath, O_CREAT | O_TRUNC, 0666, &err); ok = fd > -1; if (ok) (void) close (fd); } else { state = BOLT_FORCE_POWER_OFF; ok = bolt_unlink (statepath, &err); } if (!ok) bolt_warn_err (err, "could not write force_power state-file"); else bolt_debug (LOG_TOPIC ("power"), "wrote state %s to %s", bolt_power_state_to_string (state), statepath); power->state = state; g_object_notify_by_pspec (G_OBJECT (power), power_props[PROP_STATE]); return ok; } static char * bolt_power_gen_guard_id (BoltPower *power, GError **error) { char *id = NULL; if (g_hash_table_size (power->guards) >= G_MAXUINT16) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "maximum number of force power locks reached"); return NULL; } do { guint16 i = ++power->guard_num; g_free (id); id = g_strdup_printf ("%" G_GUINT16_FORMAT, i); } while (g_hash_table_contains (power->guards, id)); return id; } static void bolt_power_release (BoltPower *power, BoltGuard *guard) { const char *id; const char *who; gboolean ok; id = bolt_guard_get_id (guard); who = bolt_guard_get_who (guard); ok = g_hash_table_remove (power->guards, id); if (!ok) { bolt_bug ("inactive guard ('%s', '%s') found", id, who); return; } bolt_info (LOG_TOPIC ("power"), "guard '%s' for '%s' deactivated", id, who); /* we still have active guards */ if (g_hash_table_size (power->guards) != 0) return; /* go into WAIT (from ON) state */ if (power->wait_id != 0) { bolt_bug ("have active waiter already"); return; } if (power->timeout == 0) { bolt_info (LOG_TOPIC ("power"), "wait timeout is zero, skipping"); bolt_power_wait_timeout ((gpointer) power); return; } bolt_info (LOG_TOPIC ("power"), "shutdown scheduled (T-%3.2fs)", power->timeout / 1000.0); bolt_power_timeout_reset (power); } /* dbus methods */ static GVariant * handle_force_power (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error) { g_autoptr(BoltGuard) guard = NULL; g_autoptr(GUnixFDList) fds = NULL; BoltPower *power; const char *flags; const char *who; gboolean ok; guint pid; int fd; power = BOLT_POWER (object); ok = bolt_dbus_get_sender_pid (invocation, &pid, error); if (!ok) return NULL; g_variant_get (params, "(&s&s)", &who, &flags); guard = bolt_power_acquire_full (power, who, (pid_t) pid, error); if (guard == NULL) { bolt_warn_err (*error, LOG_TOPIC ("power"), "failed to acquire power for %s (pid %u)", who, pid); return NULL; } /* monitor will add a reference to guard, so freeing one * via the auto pointer is expected and in fact desired */ fd = bolt_guard_monitor (guard, error); if (fd == -1) { bolt_warn_err (*error, LOG_TOPIC ("power"), "failed to monitor guard %s for %s (pid %u)", bolt_guard_get_id (guard), who, pid); return NULL; } fds = g_unix_fd_list_new_from_array (&fd, 1); g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, g_variant_new ("(h)"), fds); return NULL; } static GVariant * handle_list_guards (BoltExported *object, GVariant *params, GDBusMethodInvocation *invocation, GError **error) { g_autoptr(GList) guards = NULL; BoltPower *power; GVariantBuilder b; power = BOLT_POWER (object); guards = bolt_power_list_guards (power); g_variant_builder_init (&b, G_VARIANT_TYPE ("a(ssu)")); for (GList *l = guards; l != NULL; l = l->next) { BoltGuard *g = BOLT_GUARD (l->data); g_variant_builder_add (&b, "(ssu)", bolt_guard_get_id (g), bolt_guard_get_who (g), bolt_guard_get_pid (g)); } return g_variant_new ("(a(ssu))", &b); } /* public methods */ BoltPower * bolt_power_new (BoltUdev *udev) { BoltPower *power; power = g_initable_new (BOLT_TYPE_POWER, NULL, NULL, "udev", udev, NULL); return power; } GFile * bolt_power_get_statedir (BoltPower *power) { g_return_val_if_fail (BOLT_IS_POWER (power), FALSE); return power->statedir; } gboolean bolt_power_can_force (BoltPower *power) { g_return_val_if_fail (BOLT_IS_POWER (power), FALSE); return power->path != NULL; } BoltPowerState bolt_power_get_state (BoltPower *power) { g_return_val_if_fail (BOLT_IS_POWER (power), -1); return power->state; } BoltGuard * bolt_power_acquire (BoltPower *power, GError **error) { const char *who = "boltd"; return bolt_power_acquire_full (power, who, 0, error); } BoltGuard * bolt_power_acquire_full (BoltPower *power, const char *who, pid_t pid, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *id = NULL; BoltGuard *guard; gboolean ok; g_return_val_if_fail (BOLT_IS_POWER (power), NULL); g_return_val_if_fail (who != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (power->path == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "force power not supported"); return FALSE; } id = bolt_power_gen_guard_id (power, error); if (id == NULL) return NULL; if (power->state == BOLT_FORCE_POWER_WAIT) { g_source_remove (power->wait_id); power->wait_id = 0; power->state = BOLT_FORCE_POWER_ON; g_object_notify_by_pspec (G_OBJECT (power), power_props[PROP_STATE]); } else if (power->state != BOLT_FORCE_POWER_ON) { ok = bolt_power_switch_toggle (power, TRUE, &err); if (!ok) { bolt_error_propagate (error, &err); return NULL; } } if (pid == 0) pid = getpid (); guard = g_object_new (BOLT_TYPE_GUARD, "id", id, "who", who, "pid", pid, NULL); g_signal_connect_object (guard, "released", (GCallback) bolt_power_release, power, G_CONNECT_SWAPPED); /* NB: we don't take a ref here, because we want the guard to * act as RAII guard, i.e. when the client releases the last * reference to the guard, the "released" signal will be * triggered and thus bolt_power_release() will be called */ g_hash_table_insert (power->guards, (gpointer) bolt_guard_get_id (guard), guard); bolt_info (LOG_TOPIC ("power"), "guard '%s' for '%s' active", id, who); /* guard is saved so we can recover our state if we * were to crash or restarted */ ok = bolt_guard_save (guard, power->statedir, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("power"), "could not save guard '%s'", id); return guard; } GList * bolt_power_list_guards (BoltPower *power) { g_return_val_if_fail (BOLT_IS_POWER (power), NULL); return g_hash_table_get_values (power->guards); } bolt-0.9.2/boltd/bolt-power.h000066400000000000000000000034121417453051000160310ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-exported.h" #include "bolt-guard.h" #include "bolt-udev.h" #include G_BEGIN_DECLS /* forward declaration */ struct udev; /* BoltPower */ #define BOLT_TYPE_POWER bolt_power_get_type () G_DECLARE_FINAL_TYPE (BoltPower, bolt_power, BOLT, POWER, BoltExported); BoltPower * bolt_power_new (BoltUdev *udev); GFile * bolt_power_get_statedir (BoltPower *power); gboolean bolt_power_can_force (BoltPower *power); BoltPowerState bolt_power_get_state (BoltPower *power); BoltGuard * bolt_power_acquire_full (BoltPower *power, const char *who, pid_t pid, GError **error); BoltGuard * bolt_power_acquire (BoltPower *power, GError **error); GList * bolt_power_list_guards (BoltPower *power); G_END_DECLS bolt-0.9.2/boltd/bolt-reaper.c000066400000000000000000000135001417453051000161450ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-reaper.h" #include "bolt-log.h" #include "bolt-unix.h" #define REAPER_TIMEOUT 20 * 1000 // seconds struct _BoltReaper { GObject object; /* */ guint timeout; /* */ GHashTable *pids; guint timeout_id; }; enum { PROP_0, PROP_TIMEOUT, PROP_LAST }; static GParamSpec *props[PROP_LAST] = { NULL, }; enum { PROCESS_DIED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE (BoltReaper, bolt_reaper, G_TYPE_OBJECT); static void bolt_reaper_finalize (GObject *object) { BoltReaper *reaper = BOLT_REAPER (object); g_clear_handle_id (&reaper->timeout_id, g_source_remove); g_hash_table_unref (reaper->pids); G_OBJECT_CLASS (bolt_reaper_parent_class)->finalize (object); } static void bolt_reaper_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltReaper *reaper = BOLT_REAPER (object); switch (prop_id) { case PROP_TIMEOUT: g_value_set_uint (value, reaper->timeout); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_reaper_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltReaper *reaper = BOLT_REAPER (object); switch (prop_id) { case PROP_TIMEOUT: reaper->timeout = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_reaper_init (BoltReaper *reaper) { reaper->pids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); } static void bolt_reaper_class_init (BoltReaperClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = bolt_reaper_finalize; gobject_class->get_property = bolt_reaper_get_property; gobject_class->set_property = bolt_reaper_set_property; props[PROP_TIMEOUT] = g_param_spec_uint ("timeout", "Timeout", NULL, 0, G_MAXINT, REAPER_TIMEOUT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); signals[PROCESS_DIED] = g_signal_new ("process-died", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); } static gboolean bolt_reaper_timeout (gpointer user_data) { BoltReaper *reaper = BOLT_REAPER (user_data); GHashTableIter iter; gpointer key, value; bolt_debug (LOG_TOPIC ("reaper"), "looking for dead processes"); g_hash_table_iter_init (&iter, reaper->pids); while (g_hash_table_iter_next (&iter, &key, &value)) { g_autofree char *name = NULL; guint pid = GPOINTER_TO_UINT (key); bolt_debug (LOG_TOPIC ("reaper"), "checking '%u'", pid); if (bolt_pid_is_alive ((pid_t) pid)) continue; bolt_info (LOG_TOPIC ("reaper"), "process '%u' is dead", pid); name = (char *) value; g_hash_table_iter_steal (&iter); g_signal_emit (reaper, signals[PROCESS_DIED], 0, pid, name); } if (g_hash_table_size (reaper->pids) == 0) { bolt_debug (LOG_TOPIC ("reaper"), "stopping"); reaper->timeout_id = 0; return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } BoltReaper * bolt_reaper_new (void) { BoltReaper *reaper; reaper = g_object_new (BOLT_TYPE_REAPER, NULL); return reaper; } void bolt_reaper_add_pid (BoltReaper *reaper, guint pid, const char *name) { gpointer p = GUINT_TO_POINTER (pid); g_return_if_fail (BOLT_IS_REAPER (reaper)); g_hash_table_insert (reaper->pids, p, g_strdup (name)); if (reaper->timeout_id != 0) return; reaper->timeout_id = g_timeout_add (reaper->timeout, bolt_reaper_timeout, reaper); bolt_info (LOG_TOPIC ("reaper"), "started"); } gboolean bolt_reaper_del_pid (BoltReaper *reaper, guint pid) { gpointer p = GUINT_TO_POINTER (pid); g_return_val_if_fail (BOLT_IS_REAPER (reaper), FALSE); return g_hash_table_remove (reaper->pids, p); } gboolean bolt_reaper_has_pid (BoltReaper *reaper, guint pid) { gpointer p = GUINT_TO_POINTER (pid); g_return_val_if_fail (BOLT_IS_REAPER (reaper), FALSE); return g_hash_table_contains (reaper->pids, p); } bolt-0.9.2/boltd/bolt-reaper.h000066400000000000000000000025511417453051000161560ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS #define BOLT_TYPE_REAPER bolt_reaper_get_type () G_DECLARE_FINAL_TYPE (BoltReaper, bolt_reaper, BOLT, REAPER, GObject); BoltReaper * bolt_reaper_new (void); void bolt_reaper_add_pid (BoltReaper *reaper, guint pid, const char *name); gboolean bolt_reaper_del_pid (BoltReaper *reaper, guint pid); gboolean bolt_reaper_has_pid (BoltReaper *reaper, guint pid); G_END_DECLS bolt-0.9.2/boltd/bolt-store.c000066400000000000000000001037341417453051000160340ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-store.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-io.h" #include "bolt-log.h" #include "bolt-str.h" #include "bolt-time.h" #include /* ************************************ */ /* BoltStore */ static void bolt_store_initable_iface_init (GInitableIface *iface); static gboolean bolt_store_initialize (GInitable *initable, GCancellable *cancellable, GError **error); static gboolean bolt_store_init_store (DIR *root, GError **error); struct _BoltStore { GObject object; GFile *root; GFile *domains; GFile *devices; GFile *keys; GFile *times; guint version; }; enum { PROP_STORE_0, PROP_ROOT, PROP_VERSION, PROP_STORE_LAST }; static GParamSpec *store_props[PROP_STORE_LAST] = { NULL, }; enum { SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_CODE (BoltStore, bolt_store, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, bolt_store_initable_iface_init)); static void bolt_store_finalize (GObject *object) { BoltStore *store = BOLT_STORE (object); g_clear_object (&store->root); g_clear_object (&store->domains); g_clear_object (&store->devices); g_clear_object (&store->keys); g_clear_object (&store->times); G_OBJECT_CLASS (bolt_store_parent_class)->finalize (object); } static void bolt_store_init (BoltStore *store) { } static void bolt_store_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltStore *store = BOLT_STORE (object); switch (prop_id) { case PROP_ROOT: g_value_set_object (value, store->root); break; case PROP_VERSION: g_value_set_uint (value, store->version); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_store_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltStore *store = BOLT_STORE (object); switch (prop_id) { case PROP_ROOT: store->root = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_store_constructed (GObject *obj) { g_autofree char *path = NULL; BoltStore *store = BOLT_STORE (obj); G_OBJECT_CLASS (bolt_store_parent_class)->constructed (obj); path = g_file_get_path (store->root); bolt_info (LOG_TOPIC ("store"), "located at: %s", path); store->devices = g_file_get_child (store->root, "devices"); store->domains = g_file_get_child (store->root, "domains"); store->keys = g_file_get_child (store->root, "keys"); store->times = g_file_get_child (store->root, "times"); } static void bolt_store_class_init (BoltStoreClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = bolt_store_finalize; gobject_class->constructed = bolt_store_constructed; gobject_class->get_property = bolt_store_get_property; gobject_class->set_property = bolt_store_set_property; store_props[PROP_ROOT] = g_param_spec_object ("root", NULL, NULL, G_TYPE_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); store_props[PROP_VERSION] = g_param_spec_uint ("version", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_STORE_LAST, store_props); signals[SIGNAL_DEVICE_ADDED] = g_signal_new ("device-added", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); signals[SIGNAL_DEVICE_REMOVED] = g_signal_new ("device-removed", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); } static void bolt_store_initable_iface_init (GInitableIface *iface) { iface->init = bolt_store_initialize; } static gboolean bolt_store_initialize (GInitable *initable, GCancellable *cancellable, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(DIR) root = NULL; g_autofree char *path = NULL; BoltStore *store = BOLT_STORE (initable); gboolean ok; path = g_file_get_path (store->root); root = bolt_opendir (path, error); if (root == NULL) return FALSE; ok = bolt_store_init_store (root, error); if (!ok) return FALSE; ok = bolt_read_uint_at (dirfd (root), "version", &store->version, &err); if (!ok && !bolt_err_notfound (err)) return bolt_error_propagate (error, &err); return TRUE; } /* internal methods */ #define DOMAIN_GROUP "domain" #define DEVICE_GROUP "device" #define USER_GROUP "user" #define CFG_FILE "boltd.conf" static gboolean bolt_store_init_store (DIR *root, GError **error) { gboolean empty; gboolean ok; /* initialize an empty store with the basic layout, * which currently is just a 'version' field, since * all other directories are created on-demand */ ok = bolt_dir_is_empty (root, &empty, error); if (!ok) return FALSE; bolt_debug (LOG_TOPIC ("store"), "needs init: %s", bolt_yesno (empty)); if (!empty) return TRUE; bolt_info (LOG_TOPIC ("store"), "initializing"); /* store is empty, create the 'version' file */ ok = bolt_write_uint_at (dirfd (root), "version", BOLT_STORE_VERSION, error); return ok; } /* public methods */ BoltStore * bolt_store_new (const char *path, GError **error) { g_autoptr(GFile) root = NULL; BoltStore *store; root = g_file_new_for_path (path); store = g_initable_new (BOLT_TYPE_STORE, NULL, error, "root", root, NULL); return store; } guint bolt_store_get_version (BoltStore *store) { g_return_val_if_fail (BOLT_IS_STORE (store), 0); return store->version; } GKeyFile * bolt_store_config_load (BoltStore *store, GError **error) { g_autoptr(GKeyFile) kf = NULL; g_autoptr(GFile) sf = NULL; g_autofree char *data = NULL; gboolean ok; gsize len; g_return_val_if_fail (store != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); sf = g_file_get_child (store->root, CFG_FILE); ok = g_file_load_contents (sf, NULL, &data, &len, NULL, error); if (!ok) return NULL; kf = g_key_file_new (); ok = g_key_file_load_from_data (kf, data, len, G_KEY_FILE_NONE, error); if (!ok) return NULL; return g_steal_pointer (&kf); } gboolean bolt_store_config_save (BoltStore *store, GKeyFile *config, GError **error) { g_autoptr(GFile) sf = NULL; g_autofree char *data = NULL; gboolean ok; gsize len; g_return_val_if_fail (store != NULL, FALSE); g_return_val_if_fail (config != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); sf = g_file_get_child (store->root, CFG_FILE); data = g_key_file_to_data (config, &len, error); if (!data) return FALSE; ok = g_file_replace_contents (sf, data, len, NULL, FALSE, 0, NULL, NULL, error); return ok; } GStrv bolt_store_list_uids (BoltStore *store, const char *type, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GDir) dir = NULL; g_autoptr(GPtrArray) ids = NULL; g_autofree char *path = NULL; const char *name; g_return_val_if_fail (BOLT_IS_STORE (store), NULL); g_return_val_if_fail (type != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (bolt_streq (type, "devices")) path = g_file_get_path (store->devices); if (bolt_streq (type, "domains")) path = g_file_get_path (store->domains); if (path == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "unknown stored typed '%s'", type); return NULL; } ids = g_ptr_array_new (); dir = g_dir_open (path, 0, &err); if (dir == NULL) { if (bolt_err_notfound (err)) return bolt_strv_from_ptr_array (&ids); bolt_error_propagate (error, &err); return NULL; } while ((name = g_dir_read_name (dir)) != NULL) { if (g_str_has_prefix (name, ".")) continue; g_ptr_array_add (ids, g_strdup (name)); } return bolt_strv_from_ptr_array (&ids); } gboolean bolt_store_put_domain (BoltStore *store, BoltDomain *domain, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GFile) entry = NULL; g_autoptr(GKeyFile) kf = NULL; g_autofree char *path = NULL; char * const * bootacl = NULL; const char *uid; gboolean ok; gsize len; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); uid = bolt_domain_get_uid (domain); g_assert (uid); entry = g_file_get_child (store->domains, uid); ok = bolt_fs_make_parent_dirs (entry, error); if (!ok) return FALSE; kf = g_key_file_new (); path = g_file_get_path (entry); ok = g_key_file_load_from_file (kf, path, G_KEY_FILE_KEEP_COMMENTS, &err); if (!ok && !bolt_err_notfound (err)) { bolt_warn_err (err, LOG_TOPIC ("store"), "error loading existing domain"); g_clear_error (&err); /* not fatal, keep going */ } bootacl = bolt_domain_get_bootacl (domain); len = bolt_strv_length (bootacl); g_key_file_set_string_list (kf, DOMAIN_GROUP, "bootacl", (const char * const *) bootacl, len); ok = g_key_file_save_to_file (kf, path, error); if (!ok) return FALSE; g_object_set (G_OBJECT (domain), "store", store, NULL); return ok; } BoltDomain * bolt_store_get_domain (BoltStore *store, const char *uid, GError **error) { g_autoptr(GKeyFile) kf = NULL; g_autoptr(GFile) db = NULL; g_autoptr(GError) err = NULL; g_auto(GStrv) bootacl = NULL; g_autofree char *path = NULL; BoltDomain *domain = NULL; gboolean ok; g_return_val_if_fail (store != NULL, NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); db = g_file_get_child (store->domains, uid); path = g_file_get_path (db); kf = g_key_file_new (); ok = g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, error); if (!ok) return NULL; bootacl = g_key_file_get_string_list (kf, DOMAIN_GROUP, "bootacl", NULL, &err); if (bootacl == NULL && !bolt_err_notfound (err)) { bolt_warn_err (err, LOG_DOM_UID (uid), LOG_TOPIC ("store"), "failed to parse bootacl for domain '%s'", uid); g_clear_error (&err); } if (bolt_strv_isempty (bootacl)) g_clear_pointer (&bootacl, g_strfreev); domain = g_object_new (BOLT_TYPE_DOMAIN, "store", store, "uid", uid, "bootacl", bootacl, NULL); return domain; } gboolean bolt_store_del_domain (BoltStore *store, BoltDomain *domain, GError **error) { g_autoptr(GFile) path = NULL; const char *uid; gboolean ok; g_return_val_if_fail (store != NULL, FALSE); g_return_val_if_fail (domain != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = bolt_domain_can_delete (domain, error); if (!ok) return FALSE; uid = bolt_domain_get_uid (domain); path = g_file_get_child (store->domains, uid); ok = g_file_delete (path, NULL, error); if (!ok) return FALSE; g_object_set (domain, "store", NULL, NULL); return TRUE; } gboolean bolt_store_put_device (BoltStore *store, BoltDevice *device, BoltPolicy policy, BoltKey *key, GError **error) { g_autoptr(GFile) entry = NULL; g_autoptr(GKeyFile) kf = NULL; g_autoptr(GError) err = NULL; g_autofree char *data = NULL; g_autofree char *path = NULL; BoltDeviceType type; const char *uid; const char *label; gboolean fresh; gboolean ok; guint64 ctime; guint64 atime; guint64 stime; guint64 gen; gsize len; guint keystate; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (BOLT_IS_DEVICE (device), FALSE); g_return_val_if_fail (key == NULL || BOLT_IS_KEY (key), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); uid = bolt_device_get_uid (device); g_assert (uid); entry = g_file_get_child (store->devices, uid); ok = bolt_fs_make_parent_dirs (entry, error); if (!ok) return FALSE; kf = g_key_file_new (); path = g_file_get_path (entry); ok = g_key_file_load_from_file (kf, path, G_KEY_FILE_KEEP_COMMENTS, &err); if (!ok) { if (!bolt_err_notfound (err)) bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DEV_UID (uid), "could not load previously stored device"); g_clear_error (&err); } g_key_file_set_string (kf, DEVICE_GROUP, "name", bolt_device_get_name (device)); g_key_file_set_string (kf, DEVICE_GROUP, "vendor", bolt_device_get_vendor (device)); gen = bolt_device_get_generation (device); if (gen) g_key_file_set_uint64 (kf, DEVICE_GROUP, "generation", gen); type = bolt_device_get_device_type (device); g_key_file_set_string (kf, DEVICE_GROUP, "type", bolt_device_type_to_string (type)); if (policy == BOLT_POLICY_DEFAULT) policy = bolt_device_get_policy (device); if (policy != BOLT_POLICY_DEFAULT) { const char *str = bolt_policy_to_string (policy); g_key_file_set_string (kf, USER_GROUP, "policy", str); } label = bolt_device_get_label (device); if (label != NULL) g_key_file_set_string (kf, USER_GROUP, "label", label); stime = bolt_device_get_storetime (device); if (stime == 0) stime = bolt_now_in_seconds (); g_key_file_set_uint64 (kf, USER_GROUP, "storetime", stime); data = g_key_file_to_data (kf, &len, error); if (!data) return FALSE; keystate = bolt_device_get_keystate (device); if (key) { ok = bolt_store_put_key (store, uid, key, &err); if (!ok) bolt_warn_err (err, "failed to store key"); else keystate = bolt_key_get_state (key); } ok = g_file_replace_contents (entry, data, len, NULL, FALSE, 0, NULL, NULL, error); if (!ok) return FALSE; fresh = bolt_device_get_stored (device) == FALSE; g_object_set (device, "store", store, "policy", policy, "key", keystate, "storetime", stime, NULL); if (fresh) g_signal_emit (store, signals[SIGNAL_DEVICE_ADDED], 0, uid); ctime = bolt_device_get_conntime (device); atime = bolt_device_get_authtime (device); bolt_store_put_times (store, uid, NULL, "conntime", ctime, "authtime", atime, NULL); return TRUE; } BoltDevice * bolt_store_get_device (BoltStore *store, const char *uid, GError **error) { g_autoptr(GKeyFile) kf = NULL; g_autoptr(GFile) db = NULL; g_autoptr(GError) err = NULL; g_autofree char *name = NULL; g_autofree char *vendor = NULL; g_autofree char *data = NULL; g_autofree char *typestr = NULL; g_autofree char *polstr = NULL; g_autofree char *label = NULL; BoltDeviceType type; BoltPolicy policy; BoltKeyState key; gboolean ok; guint64 stime; guint64 atime = 0; guint64 ctime = 0; guint gen; gsize len; g_return_val_if_fail (BOLT_IS_STORE (store), NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); db = g_file_get_child (store->devices, uid); ok = g_file_load_contents (db, NULL, &data, &len, NULL, error); if (!ok) return NULL; kf = g_key_file_new (); ok = g_key_file_load_from_data (kf, data, len, G_KEY_FILE_NONE, error); if (!ok) return NULL; name = g_key_file_get_string (kf, DEVICE_GROUP, "name", NULL); vendor = g_key_file_get_string (kf, DEVICE_GROUP, "vendor", NULL); typestr = g_key_file_get_string (kf, DEVICE_GROUP, "type", NULL); polstr = g_key_file_get_string (kf, USER_GROUP, "policy", NULL); label = g_key_file_get_string (kf, USER_GROUP, "label", NULL); gen = g_key_file_get_int64 (kf, DEVICE_GROUP, "generation", &err); if (err != NULL && !bolt_err_notfound (err)) bolt_warn_err (err, LOG_TOPIC ("store"), "invalid generation"); g_clear_error (&err); type = bolt_enum_from_string (BOLT_TYPE_DEVICE_TYPE, typestr, &err); if (type == BOLT_DEVICE_UNKNOWN_TYPE) { bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DEV_UID (uid), "invalid device type"); g_clear_error (&err); type = BOLT_DEVICE_PERIPHERAL; } policy = bolt_enum_from_string (BOLT_TYPE_POLICY, polstr, &err); if (policy == BOLT_POLICY_UNKNOWN) { bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DEV_UID (uid), "invalid policy"); g_clear_error (&err); policy = BOLT_POLICY_MANUAL; } if (label != NULL) { g_autofree char *tmp = g_steal_pointer (&label); label = bolt_strdup_validate (tmp); if (label == NULL) bolt_warn (LOG_TOPIC ("store"), LOG_DEV_UID (uid), "invalid device label: %s", label); } stime = g_key_file_get_uint64 (kf, USER_GROUP, "storetime", &err); if (err != NULL && !bolt_err_notfound (err)) bolt_warn_err (err, LOG_TOPIC ("store"), "invalid enroll-time"); if (stime == 0) { g_autoptr(GFileInfo) info = NULL; info = g_file_query_info (db, "time::changed", G_FILE_QUERY_INFO_NONE, NULL, NULL); if (info != NULL) stime = g_file_info_get_attribute_uint64 (info, "time::changed"); } key = bolt_store_have_key (store, uid); if (name == NULL || vendor == NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, "invalid device entry in store"); return NULL; } /* read timestamps, but failing is not fatal */ bolt_store_get_times (store, uid, NULL, "conntime", &ctime, "authtime", &atime, NULL); return g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", name, "vendor", vendor, "generation", gen, "type", type, "status", BOLT_STATUS_DISCONNECTED, "store", store, "policy", policy, "key", key, "storetime", stime, "conntime", ctime, "authtime", atime, "label", label, NULL); } gboolean bolt_store_del_device (BoltStore *store, const char *uid, GError **error) { g_autoptr(GFile) devpath = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); devpath = g_file_get_child (store->devices, uid); ok = g_file_delete (devpath, NULL, error); if (ok) g_signal_emit (store, signals[SIGNAL_DEVICE_REMOVED], 0, uid); return ok; } gboolean bolt_store_get_time (BoltStore *store, const char *uid, const char *timesel, guint64 *outval, GError **error) { g_autoptr(GFile) gf = NULL; g_autoptr(GFileInfo) info = NULL; g_autofree char *fn = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (timesel != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); fn = g_strdup_printf ("%s.%s", uid, timesel); gf = g_file_get_child (store->times, fn); info = g_file_query_info (gf, "time::modified", G_FILE_QUERY_INFO_NONE, NULL, error); ok = info != NULL; if (ok && outval != NULL) *outval = g_file_info_get_attribute_uint64 (info, "time::modified"); return ok; } gboolean bolt_store_get_times (BoltStore *store, const char *uid, GError **error, ...) { gboolean res = TRUE; gboolean ok = TRUE; const char *ts; va_list args; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); va_start (args, error); while ((ts = va_arg (args, const char *)) != NULL) { g_autoptr(GError) err = NULL; guint64 *val = va_arg (args, guint64 *); *val = 0; ok = bolt_store_get_time (store, uid, ts, val, &err); if (!ok && !bolt_err_notfound (err)) { if (error != NULL) { res = bolt_error_propagate (error, &err); break; } /* error variable NULL, caller doesn't care, we keep going */ bolt_warn_err (err, LOG_DEV_UID (uid), LOG_TOPIC ("store"), "failed to read timestamp '%s'", ts); } } va_end (args); return res; } gboolean bolt_store_put_time (BoltStore *store, const char *uid, const char *timesel, guint64 val, GError **error) { g_autoptr(GFile) gf = NULL; g_autofree char *fn = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (timesel != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); fn = g_strdup_printf ("%s.%s", uid, timesel); gf = g_file_get_child (store->times, fn); ok = bolt_fs_make_parent_dirs (gf, error); if (!ok) return FALSE; ok = bolt_fs_touch (gf, val, val, error); return ok; } gboolean bolt_store_put_times (BoltStore *store, const char *uid, GError **error, ...) { gboolean res = TRUE; gboolean ok = TRUE; const char *ts; va_list args; if (store == NULL) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "device '%s' is not stored", uid); return FALSE; } g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); va_start (args, error); while ((ts = va_arg (args, const char *)) != NULL) { g_autoptr(GError) err = NULL; guint64 val = va_arg (args, guint64); if (val == 0) continue; ok = bolt_store_put_time (store, uid, ts, val, &err); if (!ok) { if (error != NULL) { res = bolt_error_propagate (error, &err); break; } /* error variable NULL, caller doesn't care, we keep going */ bolt_warn_err (err, LOG_DEV_UID (uid), LOG_TOPIC ("store"), "failed to update timestamp '%s'", ts); } } va_end (args); return res; } gboolean bolt_store_del_time (BoltStore *store, const char *uid, const char *timesel, GError **error) { g_autoptr(GFile) pathfile = NULL; g_autofree char *name = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (timesel != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); name = g_strdup_printf ("%s.%s", uid, timesel); pathfile = g_file_get_child (store->times, name); ok = g_file_delete (pathfile, NULL, error); return ok; } gboolean bolt_store_del_times (BoltStore *store, const char *uid, GError **error, ...) { gboolean res = TRUE; gboolean ok = TRUE; const char *ts; va_list args; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); va_start (args, error); while ((ts = va_arg (args, const char *)) != NULL) { g_autoptr(GError) err = NULL; ok = bolt_store_del_time (store, uid, ts, &err); if (!ok && !bolt_err_notfound (err)) { if (error != NULL) { res = bolt_error_propagate (error, &err); break; } /* error variable NULL, caller doesn't care, we keep going */ bolt_warn_err (err, LOG_DEV_UID (uid), LOG_TOPIC ("store"), "failed to delete timestamp '%s'", ts); } } va_end (args); return res; } gboolean bolt_store_put_key (BoltStore *store, const char *uid, BoltKey *key, GError **error) { g_autoptr(GFile) keypath = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (BOLT_IS_KEY (key), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); keypath = g_file_get_child (store->keys, uid); ok = bolt_fs_make_parent_dirs (keypath, error); if (ok) ok = bolt_key_save_file (key, keypath, error); return ok; } BoltKeyState bolt_store_have_key (BoltStore *store, const char *uid) { g_autoptr(GFileInfo) keyinfo = NULL; g_autoptr(GFile) keypath = NULL; g_autoptr(GError) err = NULL; guint key = BOLT_KEY_MISSING; g_return_val_if_fail (BOLT_IS_STORE (store), key); g_return_val_if_fail (uid != NULL, key); keypath = g_file_get_child (store->keys, uid); keyinfo = g_file_query_info (keypath, "standard::*", 0, NULL, &err); if (keyinfo != NULL) key = BOLT_KEY_HAVE; /* todo: check size */ else if (!bolt_err_notfound (err)) bolt_warn_err (err, LOG_DEV_UID (uid), "error querying key info"); return key; } BoltKey * bolt_store_get_key (BoltStore *store, const char *uid, GError **error) { g_autoptr(GFile) keypath = NULL; g_return_val_if_fail (BOLT_IS_STORE (store), NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); keypath = g_file_get_child (store->keys, uid); return bolt_key_load_file (keypath, error); } gboolean bolt_store_del_key (BoltStore *store, const char *uid, GError **error) { g_autoptr(GFile) keypath = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); keypath = g_file_get_child (store->keys, uid); ok = g_file_delete (keypath, NULL, error); return ok; } gboolean bolt_store_del (BoltStore *store, BoltDevice *dev, GError **error) { g_autoptr(GError) err = NULL; const char *uid; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); uid = bolt_device_get_uid (dev); ok = bolt_store_del_key (store, uid, &err); if (!ok && !bolt_err_notfound (err)) { g_propagate_prefixed_error (error, g_steal_pointer (&err), "could not delete key: "); return FALSE; } ok = bolt_store_del_device (store, uid, error); if (!ok) return FALSE; bolt_store_del_times (store, uid, NULL, "conntime", "authtime", NULL); g_object_set (dev, "store", NULL, "key", BOLT_KEY_MISSING, "policy", BOLT_POLICY_DEFAULT, NULL); return ok; } BoltJournal * bolt_store_open_journal (BoltStore *store, const char *type, const char *name, GError **error) { g_autoptr(GFile) root = NULL; BoltJournal *journal; g_return_val_if_fail (store != NULL, NULL); g_return_val_if_fail (type != NULL, NULL); g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); root = g_file_get_child (store->root, type); journal = bolt_journal_new (root, name, error); return journal; } gboolean bolt_store_del_journal (BoltStore *store, const char *type, const char *name, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GFile) root = NULL; g_autoptr(GFile) journal = NULL; gboolean ok; g_return_val_if_fail (store != NULL, FALSE); g_return_val_if_fail (type != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); root = g_file_get_child (store->root, type); journal = g_file_get_child (root, name); ok = g_file_delete (journal, NULL, &err); if (!ok && !bolt_err_notfound (err)) { bolt_error_propagate (error, &err); return FALSE; } return TRUE; } gboolean bolt_store_has_journal (BoltStore *store, const char *type, const char *name) { g_autoptr(GFile) root = NULL; g_autoptr(GFile) journal = NULL; g_return_val_if_fail (store != NULL, FALSE); g_return_val_if_fail (type != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); root = g_file_get_child (store->root, type); journal = g_file_get_child (root, name); return g_file_query_exists (journal, NULL); } gboolean bolt_store_upgrade (BoltStore *store, gboolean *upgrade, GError **error) { g_autoptr(DIR) root = NULL; g_autofree char *path = NULL; gboolean need_upgrade; gboolean ok; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); need_upgrade = store->version != BOLT_STORE_VERSION; if (upgrade) *upgrade = need_upgrade; if (!need_upgrade) return TRUE; path = g_file_get_path (store->root); root = bolt_opendir (path, error); if (!root) return FALSE; ok = bolt_write_int_at (dirfd (root), ".version-upgrade", BOLT_STORE_VERSION, error); if (!ok) return FALSE; ok = bolt_renameat (dirfd (root), ".version-upgrade", dirfd (root), "version", error); if (!ok) { bolt_unlink_at (dirfd (root), ".version-upgrade", 0, NULL); return FALSE; } store->version = BOLT_STORE_VERSION; g_object_notify_by_pspec (G_OBJECT (store), store_props[PROP_VERSION]); return ok; } bolt-0.9.2/boltd/bolt-store.h000066400000000000000000000142151417453051000160340ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include "bolt-device.h" #include "bolt-domain.h" #include "bolt-enums.h" #include "bolt-key.h" #include "bolt-journal.h" G_BEGIN_DECLS #define BOLT_STORE_VERSION 1 /* BoltStore - database for devices, keys */ #define BOLT_TYPE_STORE bolt_store_get_type () G_DECLARE_FINAL_TYPE (BoltStore, bolt_store, BOLT, STORE, GObject); BoltStore * bolt_store_new (const char *path, GError **error); guint bolt_store_get_version (BoltStore *store); GKeyFile * bolt_store_config_load (BoltStore *store, GError **error); gboolean bolt_store_config_save (BoltStore *store, GKeyFile *config, GError **error); GStrv bolt_store_list_uids (BoltStore *store, const char *type, GError **error); gboolean bolt_store_put_domain (BoltStore *store, BoltDomain *domain, GError **error); BoltDomain * bolt_store_get_domain (BoltStore *store, const char *uid, GError **error); gboolean bolt_store_del_domain (BoltStore *store, BoltDomain *domain, GError **error); gboolean bolt_store_del (BoltStore *store, BoltDevice *dev, GError **error); gboolean bolt_store_put_device (BoltStore *store, BoltDevice *device, BoltPolicy policy, BoltKey *key, GError **error); BoltDevice * bolt_store_get_device (BoltStore *store, const char *uid, GError **error); gboolean bolt_store_del_device (BoltStore *store, const char *uid, GError **error); gboolean bolt_store_put_time (BoltStore *store, const char *uid, const char *timesel, guint64 val, GError **error); gboolean bolt_store_put_times (BoltStore *store, const char *uid, GError **error, ...) G_GNUC_NULL_TERMINATED; gboolean bolt_store_get_time (BoltStore *store, const char *uid, const char *timesel, guint64 *outval, GError **error); gboolean bolt_store_get_times (BoltStore *store, const char *uid, GError **error, ...) G_GNUC_NULL_TERMINATED; gboolean bolt_store_del_time (BoltStore *store, const char *uid, const char *timesel, GError **error); gboolean bolt_store_del_times (BoltStore *store, const char *uid, GError **error, ...) G_GNUC_NULL_TERMINATED; gboolean bolt_store_put_key (BoltStore *store, const char *uid, BoltKey *key, GError **error); BoltKeyState bolt_store_have_key (BoltStore *store, const char *uid); BoltKey * bolt_store_get_key (BoltStore *store, const char *uid, GError **error); gboolean bolt_store_del_key (BoltStore *store, const char *uid, GError **error); BoltJournal * bolt_store_open_journal (BoltStore *store, const char *type, const char *name, GError **error); gboolean bolt_store_del_journal (BoltStore *store, const char *type, const char *name, GError **error); gboolean bolt_store_has_journal (BoltStore *store, const char *type, const char *name); /* store upgrades */ gboolean bolt_store_upgrade (BoltStore *store, gboolean *upgrade, GError **error); G_END_DECLS bolt-0.9.2/boltd/bolt-sysfs.c000066400000000000000000000375341417453051000160530ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-sysfs.h" #include "bolt-error.h" #include "bolt-io.h" #include "bolt-log.h" #include "bolt-names.h" #include "bolt-str.h" #include #include #include #include typedef struct udev_device udev_device; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); void bolt_ident_clear (BoltIdent *id) { if (id->udev == NULL) return; udev_device_unref (id->udev); memset (id, 0, sizeof (BoltIdent)); } static const char * sysfs_get_sysattr_value (struct udev_device *dev, const char *attr, GError **error) { const char *val; g_return_val_if_fail (dev != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); val = udev_device_get_sysattr_value (dev, attr); if (val == NULL) { int code = errno; const char *path = udev_device_get_syspath (dev); g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "could not get '%s' for %s: %s", attr, path, g_strerror (code)); } return val; } const char * bolt_sysfs_device_get_unique_id (struct udev_device *dev, GError **error) { g_return_val_if_fail (dev != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); return sysfs_get_sysattr_value (dev, BOLT_SYSFS_UNIQUE_ID, error); } gint64 bolt_sysfs_device_get_time (struct udev_device *udev, BoltStatTime st) { const char *path; struct stat sb; gint64 ms = 0; int r; path = udev_device_get_syspath (udev); if (path == NULL) return 0; r = lstat (path, &sb); if (r == -1) return 0; switch (st) { case BOLT_ST_CTIME: ms = (gint64) sb.st_ctim.tv_sec; break; case BOLT_ST_ATIME: ms = (gint64) sb.st_atim.tv_sec; break; case BOLT_ST_MTIME: ms = (gint64) sb.st_mtim.tv_sec; break; } if (ms < 0) ms = 0; return ms; } gboolean bolt_sysfs_device_is_domain (struct udev_device *udev, GError **error) { const char *devtype = udev_device_get_devtype (udev); const char *subsystem = udev_device_get_subsystem (udev); gboolean is_domain; is_domain = bolt_streq (subsystem, "thunderbolt") && bolt_streq (devtype, "thunderbolt_domain"); if (!is_domain) { const char *syspath = udev_device_get_syspath (udev); g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "device '%s' is not a thunderbolt domain", syspath); } return is_domain; } struct udev_device * bolt_sysfs_domain_for_device (struct udev_device *udev, struct udev_device **host_out) { struct udev_device *parent; struct udev_device *host; gboolean found; found = FALSE; parent = udev; do { host = parent; parent = udev_device_get_parent (host); if (!parent) break; found = bolt_sysfs_device_is_domain (parent, NULL); } while (!found); if (!found) return NULL; if (host_out) *host_out = host; return parent; } BoltSecurity bolt_sysfs_security_for_device (struct udev_device *udev, GError **error) { struct udev_device *parent = NULL; const char *v; BoltSecurity s; if (bolt_sysfs_device_is_domain (udev, NULL)) parent = udev; else parent = bolt_sysfs_domain_for_device (udev, NULL); if (parent == NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_UDEV, "failed to determine domain device"); return BOLT_SECURITY_UNKNOWN; } v = udev_device_get_sysattr_value (parent, "security"); s = bolt_enum_from_string (BOLT_TYPE_SECURITY, v, error); return s; } static const char * read_sysattr_name (struct udev_device *udev, const char *attr, GError **error) { g_autofree char *s = NULL; const char *v; s = g_strdup_printf ("%s_name", attr); v = udev_device_get_sysattr_value (udev, s); if (v != NULL) return v; return sysfs_get_sysattr_value (udev, attr, error); } gboolean bolt_sysfs_device_ident (struct udev_device *udev, BoltIdent *id, GError **error) { const char *name; const char *vendor; g_return_val_if_fail (udev != NULL, FALSE); g_return_val_if_fail (id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); vendor = read_sysattr_name (udev, "vendor", error); if (vendor == NULL) return FALSE; name = read_sysattr_name (udev, "device", error); if (name == NULL) return FALSE; id->udev = udev_device_ref (udev); id->name = name; id->vendor = vendor; return TRUE; } gboolean bolt_sysfs_host_ident (struct udev_device *dev, BoltIdent *id, GError **error) { g_autoptr(udev_device) dmi = NULL; struct udev *udev; const char *name; const char *vendor; const char *attr; gboolean ok; g_return_val_if_fail (dev != NULL, FALSE); g_return_val_if_fail (id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* First check if the host controller has normal device ident, * which should be present for all controller that have a DROM. */ ok = bolt_sysfs_device_ident (dev, id, NULL); if (ok) return TRUE; /* On embedded thunderbolt controllers without DROM, we * fall back to using the SMBIOS/DMI system information */ udev = udev_device_get_udev (dev); dmi = udev_device_new_from_syspath (udev, BOLT_SYSFS_DMI_ID); if (dmi == NULL) { int code = errno; g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "could not open dmi/id device: %s", g_strerror (code)); return FALSE; } attr = BOLT_SYSFS_DMI_SYS_VENDOR; vendor = sysfs_get_sysattr_value (dmi, attr, error); if (vendor == NULL) return FALSE; /* Almost all systems are using the product_name attribute * for the human readable string, with exception of Lenovo, * that instead uses the product_version, so we special * case that. */ attr = BOLT_SYSFS_DMI_PRODUCT_NAME; if (!g_ascii_strcasecmp (vendor, "lenovo")) { attr = BOLT_SYSFS_DMI_PRODUCT_VERSION; vendor = "Lenovo"; } name = sysfs_get_sysattr_value (dmi, attr, error); if (name == NULL) return FALSE; id->udev = g_steal_pointer (&dmi); id->name = name; id->vendor = vendor; return TRUE; } static int bolt_syfs_count_tb_devices (struct udev *udev, struct udev_device *parent, GError **error) { struct udev_enumerate *e; struct udev_list_entry *l, *devices; int r, count = 0; e = udev_enumerate_new (udev); udev_enumerate_add_match_subsystem (e, "thunderbolt"); udev_enumerate_add_match_property (e, "DEVTYPE", "thunderbolt_device"); if (parent) udev_enumerate_add_match_parent (e, parent); r = udev_enumerate_scan_devices (e); if (r < 0) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "failed to scan udev: %s", g_strerror (-r)); return r; } devices = udev_enumerate_get_list_entry (e); udev_list_entry_foreach (l, devices) count++; udev_enumerate_unref (e); return count; } int bolt_sysfs_count_hosts (struct udev *udev, GError **error) { struct udev_enumerate *e; struct udev_list_entry *l, *devices; int r, count = 0; e = udev_enumerate_new (udev); udev_enumerate_add_match_subsystem (e, "thunderbolt"); udev_enumerate_add_match_property (e, "DEVTYPE", "thunderbolt_domain"); r = udev_enumerate_scan_devices (e); if (r < 0) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "failed to scan udev: %s", g_strerror (-r)); return r; } devices = udev_enumerate_get_list_entry (e); udev_list_entry_foreach (l, devices) { struct udev_device *udevice = NULL; const char *syspath; int n; syspath = udev_list_entry_get_name (l); udevice = udev_device_new_from_syspath (udev, syspath); if (udevice == NULL) continue; n = bolt_syfs_count_tb_devices (udev, udevice, NULL); if (n > 0) count++; udev_device_unref (udevice); } udev_enumerate_unref (e); return count; } gboolean bolt_sysfs_nhi_id_for_domain (struct udev_device *udev, guint32 *id, GError **error) { struct udev_device *parent; const char *str; gboolean ok; ok = bolt_sysfs_device_is_domain (udev, error); if (!ok) return FALSE; parent = udev_device_get_parent (udev); if (parent == NULL) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "failed to get parent for domain: %s", g_strerror (errno)); return FALSE; } str = udev_device_get_sysattr_value (parent, "device"); if (str == NULL) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "failed to get PCI id for NHI device: %s", g_strerror (errno)); return FALSE; } return bolt_str_parse_as_uint32 (str, id, error); } static gint sysfs_get_sysattr_value_as_int (struct udev_device *udev, const char *attr) { const char *str; gboolean ok; gint val; g_return_val_if_fail (udev != NULL, FALSE); str = udev_device_get_sysattr_value (udev, attr); if (str == NULL) return -errno; ok = bolt_str_parse_as_int (str, &val, NULL); if (!ok) return -errno; return val; } static gssize sysfs_get_sysattr_size (struct udev_device *udev, const char *attr) { const char *str; g_return_val_if_fail (udev != NULL, FALSE); str = udev_device_get_sysattr_value (udev, attr); if (str == NULL) return -errno; return strlen (str); } void bolt_sysfs_read_link_speed (struct udev_device *udev, BoltLinkSpeed *speed) { struct { const char *name; guint32 *item; } entries[] = { {BOLT_SYSFS_RX_LANES, &speed->rx.lanes}, {BOLT_SYSFS_RX_SPEED, &speed->rx.speed}, {BOLT_SYSFS_TX_LANES, &speed->tx.lanes}, {BOLT_SYSFS_TX_SPEED, &speed->tx.speed}, }; for (unsigned i = 0; i < G_N_ELEMENTS (entries); i++) { const char *name = entries[i].name; int res = sysfs_get_sysattr_value_as_int (udev, name); *entries[i].item = res > 0 ? res : 0; } } gboolean bolt_sysfs_info_for_device (struct udev_device *udev, gboolean full, BoltDevInfo *info, GError **error) { struct udev_device *parent; int auth; int gen; g_return_val_if_fail (udev != NULL, FALSE); g_return_val_if_fail (info != NULL, FALSE); info->keysize = -1; info->ctim = -1; info->full = FALSE; info->parent = NULL; info->generation = 0; info->syspath = NULL; auth = sysfs_get_sysattr_value_as_int (udev, "authorized"); info->authorized = auth; if (auth < 0) { int code = g_io_error_from_errno (errno); g_set_error (error, G_IO_ERROR, code, "could not read 'authorized': %s", g_strerror (errno)); return FALSE; } info->keysize = sysfs_get_sysattr_size (udev, "key"); info->boot = sysfs_get_sysattr_value_as_int (udev, "boot"); if (full == FALSE) return TRUE; info->full = TRUE; info->ctim = bolt_sysfs_device_get_time (udev, BOLT_ST_CTIME); info->syspath = udev_device_get_syspath (udev); parent = udev_device_get_parent (udev); if (parent != NULL) info->parent = udev_device_get_sysattr_value (parent, "unique_id"); gen = sysfs_get_sysattr_value_as_int (udev, BOLT_SYSFS_GENERATION); if (gen > 0) info->generation = gen; bolt_sysfs_read_link_speed (udev, &info->linkspeed); return TRUE; } gboolean bolt_sysfs_read_boot_acl (struct udev_device *udev, GStrv *out, GError **error) { g_auto(GStrv) acl = NULL; const char *val; g_return_val_if_fail (udev != NULL, FALSE); g_return_val_if_fail (out != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); val = udev_device_get_sysattr_value (udev, "boot_acl"); if (val) acl = g_strsplit (val, ",", 1024); else if (errno != ENOENT) return bolt_error_for_errno (error, errno, "%m"); /* if the attribute exists but is empty, return NULL */ if (!bolt_strv_isempty (acl)) *out = g_steal_pointer (&acl); else *out = NULL; return TRUE; } gboolean bolt_sysfs_write_boot_acl (const char *device, GStrv acl, GError **error) { g_autofree char *val = NULL; g_autofree char *path = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (acl != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); val = g_strjoinv (",", acl); path = g_build_filename (device, "boot_acl", NULL); return bolt_file_write_all (path, val, -1, error); } gboolean bolt_sysfs_read_iommu (struct udev_device *udev, gboolean *out, GError **error) { int val = 0; g_return_val_if_fail (udev != NULL, FALSE); g_return_val_if_fail (out != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); val = sysfs_get_sysattr_value_as_int (udev, BOLT_SYSFS_IOMMU); if (val < 0 && val != -ENOENT) return bolt_error_for_errno (error, errno, "failed to read %s: %s", BOLT_SYSFS_IOMMU, g_strerror (-val)); *out = val > 0; return TRUE; } /* NHI PCI id related */ static struct { guint32 pci_id; gboolean stable; /* Does the UUID change on reboot */ } nhi_table[] = { {0x157d, TRUE}, // WIN_RIDGE_2C_NHI {0x15bf, TRUE}, // ALPINE_RIDGE_LP_NHI {0x15d2, TRUE}, // ALPINE_RIDGE_C_4C_NHI {0x15d9, TRUE}, // ALPINE_RIDGE_C_2C_NHI {0x15dc, TRUE}, // ALPINE_RIDGE_LP_USBONLY_NHI {0x15dd, TRUE}, // ALPINE_RIDGE_USBONLY_NH {0x15de, TRUE}, // ALPINE_RIDGE_C_USBONLY_NHI {0x15e8, TRUE}, // TITAN_RIDGE_2C_NHI {0x15eb, TRUE}, // TITAN_RIDGE_4C_NHI {0x8a0d, FALSE}, // ICL_NHI1 {0x8a17, FALSE}, // ICL_NHI0 {0x9a1b, FALSE}, // TGL_NHI0 {0x9a1d, FALSE}, // TGL_NHI1 }; gboolean bolt_nhi_uuid_is_stable (guint32 pci_id, gboolean *stability, GError **error) { g_return_val_if_fail (stability != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); for (gsize i = 0; i < G_N_ELEMENTS (nhi_table); i++) { if (pci_id == nhi_table[i].pci_id) { *stability = nhi_table[i].stable; return TRUE; } } g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "unknown NHI PCI id '0x%04x'", pci_id); return FALSE; } bolt-0.9.2/boltd/bolt-sysfs.h000066400000000000000000000110211417453051000160370ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-wire.h" #include struct udev; struct udev_device; G_BEGIN_DECLS /* Device identification */ typedef struct _BoltIdent BoltIdent; struct _BoltIdent { struct udev_device *udev; const char *name; const char *vendor; }; #define BOLT_IDENT_INIT {NULL, NULL, NULL} void bolt_ident_clear (BoltIdent *id); G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (BoltIdent, bolt_ident_clear); const char * bolt_sysfs_device_get_unique_id (struct udev_device *dev, GError **error); typedef enum BoltStatTime { BOLT_ST_ATIME, BOLT_ST_CTIME, BOLT_ST_MTIME } BoltStatTime; gint64 bolt_sysfs_device_get_time (struct udev_device *udev, BoltStatTime st); gboolean bolt_sysfs_device_is_domain (struct udev_device *udev, GError **error); struct udev_device * bolt_sysfs_domain_for_device (struct udev_device *udev, struct udev_device **host); BoltSecurity bolt_sysfs_security_for_device (struct udev_device *udev, GError **error); gboolean bolt_sysfs_device_ident (struct udev_device *udev, BoltIdent *id, GError **error); gboolean bolt_sysfs_host_ident (struct udev_device *udev, BoltIdent *id, GError **error); int bolt_sysfs_count_hosts (struct udev *udev, GError **error); gboolean bolt_sysfs_nhi_id_for_domain (struct udev_device *udev, guint32 *id, GError **error); typedef struct _BoltDevInfo { /* always included */ gint authorized; gssize keysize; gint boot; /* if 'full' is true the rest is valid */ gboolean full; gint64 ctim; const char *syspath; const char *parent; /* the uid */ guint generation; /* link speed, may be 0 if unknown */ BoltLinkSpeed linkspeed; } BoltDevInfo; gboolean bolt_sysfs_info_for_device (struct udev_device *udev, gboolean full, BoltDevInfo *info, GError **error); void bolt_sysfs_read_link_speed (struct udev_device *udev, BoltLinkSpeed *speed); gboolean bolt_sysfs_read_boot_acl (struct udev_device *udev, GStrv *out, GError **error); gboolean bolt_sysfs_write_boot_acl (const char *device, GStrv acl, GError **error); gboolean bolt_sysfs_read_iommu (struct udev_device *udev, gboolean *out, GError **error); /* NHI id related functions*/ gboolean bolt_nhi_uuid_is_stable (guint32 pci_id, gboolean *stability, GError **error); G_END_DECLS bolt-0.9.2/boltd/bolt-udev.c000066400000000000000000000264501417453051000156420ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-udev.h" #include "bolt-error.h" #include "bolt-sysfs.h" #include #include #include typedef struct udev_monitor udev_monitor; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_monitor, udev_monitor_unref); typedef struct udev_device udev_device; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); static void udev_initable_iface_init (GInitableIface *iface); static gboolean bolt_udev_initialize (GInitable *initable, GCancellable *cancellable, GError **error); /* */ struct _BoltUdev { GObject object; /* the native udev things */ struct udev *udev; struct udev_monitor *monitor; GSource *source; /* properties */ char *name; GStrv filter; }; enum { PROP_0, PROP_NAME, PROP_FILTER, PROP_LAST }; static GParamSpec *props[PROP_LAST] = { NULL, }; enum { SIGNAL_UEVENT, SIGNAL_LAST, }; static guint signals[SIGNAL_LAST] = { 0, }; G_DEFINE_TYPE_WITH_CODE (BoltUdev, bolt_udev, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, udev_initable_iface_init)); static void bolt_udev_finalize (GObject *object) { BoltUdev *udev = BOLT_UDEV (object); if (udev->monitor) { udev_monitor_unref (udev->monitor); udev->monitor = NULL; g_source_destroy (udev->source); g_source_unref (udev->source); udev->source = NULL; } g_clear_pointer (&udev->udev, udev_unref); g_clear_pointer (&udev->name, g_free); g_clear_pointer (&udev->filter, g_strfreev); G_OBJECT_CLASS (bolt_udev_parent_class)->finalize (object); } static void bolt_udev_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltUdev *udev = BOLT_UDEV (object); switch (prop_id) { case PROP_NAME: g_value_set_string (value, udev->name); break; case PROP_FILTER: g_value_set_boxed (value, udev->filter); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_udev_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltUdev *udev = BOLT_UDEV (object); switch (prop_id) { case PROP_NAME: udev->name = g_value_dup_string (value); break; case PROP_FILTER: udev->filter = g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_udev_init (BoltUdev *udev) { } static void bolt_udev_class_init (BoltUdevClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bolt_udev_get_property; gobject_class->set_property = bolt_udev_set_property; gobject_class->finalize = bolt_udev_finalize; props[PROP_NAME] = g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_FILTER] = g_param_spec_boxed ("filter", NULL, NULL, G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); signals[SIGNAL_UEVENT] = g_signal_new ("uevent", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER); } static void udev_initable_iface_init (GInitableIface *iface) { iface->init = bolt_udev_initialize; } /* internal methods */ static gboolean monitor_add_filter (struct udev_monitor *monitor, const char *subsystem_devtype, GError **error) { g_autofree char *subsystem = NULL; char *devtype = NULL; gboolean ok; int r; subsystem = g_strdup (subsystem_devtype); devtype = strchr (subsystem, '/'); if (devtype != NULL) *devtype++ = '\0'; r = udev_monitor_filter_add_match_subsystem_devtype (monitor, subsystem, devtype); ok = r > -1; if (!ok) g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "udev: could not add match for '%s' (%s) to monitor", subsystem, devtype ? : "*"); return ok; } static gboolean setup_monitor (BoltUdev *udev, const char *name, const GStrv filter, GSourceFunc callback, udev_monitor **monitor_out, GSource **watch_out, GError **error) { g_autoptr(udev_monitor) monitor = NULL; g_autoptr(GIOChannel) channel = NULL; GSource *watch; gboolean ok; int fd; int res; monitor = udev_monitor_new_from_netlink (udev->udev, name); if (monitor == NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_UDEV, "udev: could not create monitor"); return FALSE; } udev_monitor_set_receive_buffer_size (monitor, 128 * 1024 * 1024); for (guint i = 0; filter && filter[i] != NULL; i++) { ok = monitor_add_filter (monitor, filter[i], error); if (!ok) return FALSE; } res = udev_monitor_enable_receiving (monitor); if (res < 0) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_UDEV, "udev: could not enable monitoring"); return FALSE; } fd = udev_monitor_get_fd (monitor); if (fd < 0) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_UDEV, "udev: could not obtain fd for monitoring"); return FALSE; } channel = g_io_channel_unix_new (fd); watch = g_io_create_watch (channel, G_IO_IN); g_source_set_callback (watch, callback, udev, NULL); g_source_attach (watch, g_main_context_get_thread_default ()); *monitor_out = udev_monitor_ref (monitor); *watch_out = watch; return TRUE; } static gboolean handle_uevent_udev (GIOChannel *source, GIOCondition condition, gpointer user_data) { g_autoptr(udev_device) device = NULL; BoltUdev *udev; const char *action; const char *syspath; udev = BOLT_UDEV (user_data); device = udev_monitor_receive_device (udev->monitor); if (device == NULL) return G_SOURCE_CONTINUE; action = udev_device_get_action (device); if (action == NULL) return G_SOURCE_CONTINUE; syspath = udev_device_get_syspath (device); if (syspath == NULL) return G_SOURCE_CONTINUE; g_signal_emit (udev, signals[SIGNAL_UEVENT], 0, action, device); return G_SOURCE_CONTINUE; } static gboolean bolt_udev_initialize (GInitable *initable, GCancellable *cancellable, GError **error) { BoltUdev *udev = BOLT_UDEV (initable); gboolean ok; udev->udev = udev_new (); if (udev->udev == NULL) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_UDEV, "udev: could not create udev handle"); return FALSE; } ok = setup_monitor (udev, udev->name, udev->filter, (GSourceFunc) handle_uevent_udev, &udev->monitor, &udev->source, error); return ok; } /* public methods */ BoltUdev * bolt_udev_new (const char *name, const char * const *filter, GError **error) { BoltUdev *udev; udev = g_initable_new (BOLT_TYPE_UDEV, NULL, error, "name", name, "filter", filter, NULL); return udev; } struct udev_enumerate * bolt_udev_new_enumerate (BoltUdev *udev, GError **error) { struct udev_enumerate *e; g_return_val_if_fail (BOLT_IS_UDEV (udev), NULL); e = udev_enumerate_new (udev->udev); if (e == NULL) g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "could not enumerate udev: %s", g_strerror (errno)); return e; } struct udev_device * bolt_udev_device_new_from_syspath (BoltUdev *udev, const char *syspath, GError **error) { struct udev_device *dev; g_return_val_if_fail (BOLT_IS_UDEV (udev), NULL); g_return_val_if_fail (syspath != NULL, NULL); dev = udev_device_new_from_syspath (udev->udev, syspath); if (dev == NULL) g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, "could not create udev device: %s", g_strerror (errno)); return dev; } /* thunderbolt specific helpers */ int bolt_udev_count_hosts (BoltUdev *udev, GError **error) { g_return_val_if_fail (BOLT_IS_UDEV (udev), -1); return bolt_sysfs_count_hosts (udev->udev, error); } gboolean bolt_udev_detect_force_power (BoltUdev *udev, char **result, GError **error) { struct udev_enumerate *e; struct udev_list_entry *l, *devices; g_return_val_if_fail (BOLT_IS_UDEV (udev), FALSE); g_return_val_if_fail (result != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); e = bolt_udev_new_enumerate (udev, NULL); udev_enumerate_add_match_subsystem (e, "wmi"); udev_enumerate_add_match_property (e, "DRIVER", "intel-wmi-thunderbolt"); udev_enumerate_scan_devices (e); devices = udev_enumerate_get_list_entry (e); udev_list_entry_foreach (l, devices) { g_autofree char *path = NULL; const char *syspath; syspath = udev_list_entry_get_name (l); path = g_build_filename (syspath, "force_power", NULL); if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) { *result = g_steal_pointer (&path); break; } } udev_enumerate_unref (e); return TRUE; } bolt-0.9.2/boltd/bolt-udev.h000066400000000000000000000037201417453051000156420ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS /* forward declaration */ struct udev; struct udev_device; struct udev_enumerate; /* BoltUdev - small udev abstraction */ #define BOLT_TYPE_UDEV bolt_udev_get_type () G_DECLARE_FINAL_TYPE (BoltUdev, bolt_udev, BOLT, UDEV, GObject); BoltUdev * bolt_udev_new (const char *name, const char * const *filter, GError **error); struct udev_enumerate * bolt_udev_new_enumerate (BoltUdev *udev, GError **error); struct udev_device * bolt_udev_device_new_from_syspath (BoltUdev *udev, const char *syspath, GError **error); /* thunderbolt specific helpers */ int bolt_udev_count_hosts (BoltUdev *udev, GError **error); gboolean bolt_udev_detect_force_power (BoltUdev *udev, char **path, GError **error); G_END_DECLS bolt-0.9.2/boltd/bolt-watchdog.c000066400000000000000000000137201417453051000164730ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-watchdog.h" #include "bolt-log.h" #include "bolt-str.h" #include "bolt-time.h" #include "bolt-unix.h" #include /* prototypes */ static void watchdog_initable_iface_init (GInitableIface *iface); static gboolean bolt_watchdog_initialize (GInitable *initable, GCancellable *cancellable, GError **error); static gboolean bolt_watchdog_on_pulse (gpointer user_data); /* */ struct _BoltWatchdog { GObject object; /* */ guint64 timeout; /* the actual timeout (usec) */ guint64 pulse; /* the calculated pulse (sec) */ guint pulse_id; /* source id for the pulse */ }; enum { PROP_0, PROP_TIMEOUT, PROP_PULSE, PROP_LAST }; static GParamSpec *props[PROP_LAST] = { NULL, }; G_DEFINE_TYPE_WITH_CODE (BoltWatchdog, bolt_watchdog, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, watchdog_initable_iface_init)); static void bolt_watchdog_finalize (GObject *object) { BoltWatchdog *dog = BOLT_WATCHDOG (object); g_clear_handle_id (&dog->pulse_id, g_source_remove); G_OBJECT_CLASS (bolt_watchdog_parent_class)->finalize (object); } static void bolt_watchdog_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BoltWatchdog *dog = BOLT_WATCHDOG (object); switch (prop_id) { case PROP_TIMEOUT: g_value_set_uint64 (value, dog->timeout); break; case PROP_PULSE: g_value_set_uint (value, dog->pulse); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_watchdog_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BoltWatchdog *dog = BOLT_WATCHDOG (object); switch (prop_id) { case PROP_TIMEOUT: dog->timeout = g_value_get_uint64 (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bolt_watchdog_init (BoltWatchdog *dog) { } static void bolt_watchdog_class_init (BoltWatchdogClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bolt_watchdog_get_property; gobject_class->set_property = bolt_watchdog_set_property; gobject_class->finalize = bolt_watchdog_finalize; props[PROP_TIMEOUT] = g_param_spec_uint64 ("timeout", "Timeout", NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_PULSE] = g_param_spec_uint ("pulse", "Pulse", NULL, 0, G_MAXUINT - 1, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); } static void watchdog_initable_iface_init (GInitableIface *iface) { iface->init = bolt_watchdog_initialize; } static gboolean bolt_watchdog_initialize (GInitable *initable, GCancellable *cancellable, GError **error) { BoltWatchdog *dog = BOLT_WATCHDOG (initable); guint64 quot, rem; guint pulse; guint tid; int r; r = bolt_sd_watchdog_enabled (&dog->timeout, error); if (r < 0) return FALSE; else if (r == 0) return TRUE; quot = dog->timeout / G_USEC_PER_SEC; rem = dog->timeout % G_USEC_PER_SEC; if (quot < 2 || quot >= G_MAXUINT) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid timeout: %" G_GUINT64_FORMAT, dog->timeout); return FALSE; } if (rem != 0) bolt_warn (LOG_TOPIC ("watchdog"), "sub-second precision timeout: " "%" G_GUINT64_FORMAT ". Rounding down.", rem); /* we send a pulse/ping right in the middle of the timeout * period to ensure we never miss one */ pulse = quot / 2; tid = g_timeout_add_seconds (pulse, bolt_watchdog_on_pulse, dog); dog->pulse_id = tid; dog->pulse = pulse; /* we have an active timeout enabled */ bolt_info (LOG_TOPIC ("watchdog"), "enabled [pulse: %lus]", pulse); return TRUE; } /* internal methods */ static gboolean bolt_watchdog_on_pulse (gpointer user_data) { g_autoptr(GError) err = NULL; gboolean ok; gboolean sent; ok = bolt_sd_notify_literal ("WATCHDOG=1", &sent, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("watchdog"), "failed to send ping"); else bolt_debug (LOG_TOPIC ("watchdog"), "ping [sent: %s]", bolt_yesno (sent)); return G_SOURCE_CONTINUE; } /* public methods */ BoltWatchdog * bolt_watchdog_new (GError **error) { BoltWatchdog *watchdog; watchdog = g_initable_new (BOLT_TYPE_WATCHDOG, NULL, error, NULL); return watchdog; } bolt-0.9.2/boltd/bolt-watchdog.h000066400000000000000000000020771417453051000165030ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS /* BoltWatchdog - Idle and status tracking */ #define BOLT_TYPE_WATCHDOG bolt_watchdog_get_type () G_DECLARE_FINAL_TYPE (BoltWatchdog, bolt_watchdog, BOLT, WATCHDOG, GObject); BoltWatchdog * bolt_watchdog_new (GError **error); G_END_DECLS bolt-0.9.2/cli/000077500000000000000000000000001417453051000132315ustar00rootroot00000000000000bolt-0.9.2/cli/bolt-client.c000066400000000000000000001064041417453051000156160ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "bolt-client.h" #include "bolt-device.h" #include "bolt-error.h" #include "bolt-names.h" #include "bolt-str.h" #include static void handle_dbus_device_added (GObject *self, GDBusProxy *bus_proxy, GVariant *params); static void handle_dbus_device_removed (GObject *self, GDBusProxy *bus_proxy, GVariant *params); static void handle_dbus_domain_added (GObject *self, GDBusProxy *bus_proxy, GVariant *params); static void handle_dbus_domain_removed (GObject *self, GDBusProxy *bus_proxy, GVariant *params); struct _BoltClient { BoltProxy parent; }; enum { PROP_0, /* D-Bus Props */ PROP_VERSION, PROP_PROBING, PROP_POLICY, PROP_SECURITY, PROP_AUTHMODE, PROP_POWERSTATE, PROP_LAST }; static GParamSpec *props[PROP_LAST] = {NULL, }; enum { SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DOMAIN_ADDED, SIGNAL_DOMAIN_REMOVED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE (BoltClient, bolt_client, BOLT_TYPE_PROXY); static const BoltProxySignal * bolt_client_get_dbus_signals (guint *n) { static BoltProxySignal dbus_signals[] = { {"DeviceAdded", handle_dbus_device_added}, {"DeviceRemoved", handle_dbus_device_removed}, {"DomainAdded", handle_dbus_domain_added}, {"DomainRemoved", handle_dbus_domain_removed}, }; *n = G_N_ELEMENTS (dbus_signals); return dbus_signals; } static void bolt_client_class_init (BoltClientClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); BoltProxyClass *proxy_class = BOLT_PROXY_CLASS (klass); gobject_class->get_property = bolt_proxy_property_getter; gobject_class->set_property = bolt_proxy_property_setter; proxy_class->get_dbus_signals = bolt_client_get_dbus_signals; props[PROP_VERSION] = g_param_spec_uint ("version", "Version", "D-Bus API revision.", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_PROBING] = g_param_spec_boolean ("probing", "Probing", "Is the daemon processing new devices?", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_POLICY] = g_param_spec_enum ("default-policy", "DefaultPolicy", "Policy to use if none was specified.", BOLT_TYPE_POLICY, BOLT_POLICY_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_SECURITY] = g_param_spec_enum ("security-level", "SecurityLevel", "The global security level.", BOLT_TYPE_SECURITY, BOLT_SECURITY_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_AUTHMODE] = g_param_spec_flags ("auth-mode", "AuthMode", "Current authentication mode.", BOLT_TYPE_AUTH_MODE, BOLT_AUTH_ENABLED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_POWERSTATE] = g_param_spec_enum ("power-state", "PowerState", "Current force power state.", BOLT_TYPE_POWER_STATE, BOLT_FORCE_POWER_UNSET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); /* signals */ signals[SIGNAL_DEVICE_ADDED] = g_signal_new ("device-added", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); signals[SIGNAL_DEVICE_REMOVED] = g_signal_new ("device-removed", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); signals[SIGNAL_DOMAIN_ADDED] = g_signal_new ("domain-added", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); signals[SIGNAL_DOMAIN_REMOVED] = g_signal_new ("domain-removed", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); } static void bolt_client_init (BoltClient *cli) { /* we don't want this to be optimized away */ volatile GQuark error_domain; /* initialize error mapping */ error_domain = BOLT_ERROR; (error_domain); /* To avoid -Wunused-but-set-variable */ } /* dbus signals */ static void handle_dbus_device_added (GObject *self, GDBusProxy *bus_proxy, GVariant *params) { BoltClient *cli = BOLT_CLIENT (self); const char *opath = NULL; g_variant_get_child (params, 0, "&o", &opath); g_signal_emit (cli, signals[SIGNAL_DEVICE_ADDED], 0, opath); } static void handle_dbus_device_removed (GObject *self, GDBusProxy *bus_proxy, GVariant *params) { BoltClient *cli = BOLT_CLIENT (self); const char *opath = NULL; g_variant_get_child (params, 0, "&o", &opath); g_signal_emit (cli, signals[SIGNAL_DEVICE_REMOVED], 0, opath); } static void handle_dbus_domain_added (GObject *self, GDBusProxy *bus_proxy, GVariant *params) { BoltClient *cli = BOLT_CLIENT (self); const char *opath = NULL; g_variant_get_child (params, 0, "&o", &opath); g_signal_emit (cli, signals[SIGNAL_DOMAIN_ADDED], 0, opath); } static void handle_dbus_domain_removed (GObject *self, GDBusProxy *bus_proxy, GVariant *params) { BoltClient *cli = BOLT_CLIENT (self); const char *opath = NULL; g_variant_get_child (params, 0, "&o", &opath); g_signal_emit (cli, signals[SIGNAL_DOMAIN_REMOVED], 0, opath); } /* public methods */ BoltClient * bolt_client_new (GError **error) { BoltClient *cli; GDBusConnection *bus; g_return_val_if_fail (error == NULL || *error == NULL, NULL); bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (bus == NULL) { g_prefix_error (error, "Error connecting to D-Bus: "); return FALSE; } cli = g_initable_new (BOLT_TYPE_CLIENT, NULL, error, "g-flags", G_DBUS_PROXY_FLAGS_NONE, "g-connection", bus, "g-name", BOLT_DBUS_NAME, "g-object-path", BOLT_DBUS_PATH, "g-interface-name", BOLT_DBUS_INTERFACE, NULL); g_object_unref (bus); return cli; } static void got_the_client (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = user_data; GError *error = NULL; GObject *obj; obj = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, &error); if (obj == NULL) { /* error ownership gets transferred to the task */ g_task_return_error (task, error); return; } g_task_return_pointer (task, obj, g_object_unref); } static void got_the_bus (GObject *source, GAsyncResult *res, gpointer user_data) { GError *error = NULL; GTask *task = user_data; GCancellable *cancellable; GDBusConnection *bus; bus = g_bus_get_finish (res, &error); if (bus == NULL) { g_prefix_error (&error, "could not connect to D-Bus: "); /* error ownership gets transferred to the task */ g_task_return_error (task, error); g_object_unref (task); return; } cancellable = g_task_get_cancellable (task); g_async_initable_new_async (BOLT_TYPE_CLIENT, G_PRIORITY_DEFAULT, cancellable, got_the_client, task, "g-flags", G_DBUS_PROXY_FLAGS_NONE, "g-connection", bus, "g-name", BOLT_DBUS_NAME, "g-object-path", BOLT_DBUS_PATH, "g-interface-name", BOLT_DBUS_INTERFACE, NULL); g_object_unref (bus); } void bolt_client_new_async (GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback == NULL); task = g_task_new (NULL, cancellable, callback, user_data); g_bus_get (G_BUS_TYPE_SYSTEM, cancellable, got_the_bus, task); } BoltClient * bolt_client_new_finish (GAsyncResult *res, GError **error) { g_return_val_if_fail (G_IS_TASK (res), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); return g_task_propagate_pointer (G_TASK (res), error); } GPtrArray * bolt_client_list_domains (BoltClient *client, GCancellable *cancel, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GPtrArray) domains = NULL; g_autoptr(GVariantIter) iter = NULL; GDBusConnection *bus = NULL; const char *d; g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), "ListDomains", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancel, error); if (val == NULL) return NULL; bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); domains = g_ptr_array_new_with_free_func (g_object_unref); g_variant_get (val, "(ao)", &iter); while (g_variant_iter_loop (iter, "&o", &d, NULL)) { BoltDomain *dom; dom = bolt_domain_new_for_object_path (bus, d, cancel, error); if (dom == NULL) return NULL; g_ptr_array_add (domains, dom); } return g_steal_pointer (&domains); } BoltDomain * bolt_client_find_domain (BoltClient *client, const char *name, GError **error) { g_autoptr(GPtrArray) domains = NULL; domains = bolt_client_list_domains (client, NULL, error); if (domains == NULL) return NULL; for (guint i = 0; i < domains->len; i++) { BoltDomain *dom = g_ptr_array_index (domains, i); if (bolt_streq (name, bolt_domain_get_uid (dom)) || bolt_streq (name, bolt_domain_get_id (dom))) return g_object_ref (dom); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "could not find domain matching '%s'", name); return NULL; } GPtrArray * bolt_client_list_devices (BoltClient *client, GCancellable *cancel, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GVariantIter) iter = NULL; GDBusConnection *bus = NULL; const char *d; g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), "ListDevices", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancel, error); if (val == NULL) return NULL; bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); devices = g_ptr_array_new_with_free_func (g_object_unref); g_variant_get (val, "(ao)", &iter); while (g_variant_iter_loop (iter, "&o", &d, NULL)) { BoltDevice *dev; dev = bolt_device_new_for_object_path (bus, d, cancel, error); if (dev == NULL) return NULL; g_ptr_array_add (devices, dev); } return g_steal_pointer (&devices); } GPtrArray * bolt_client_list_parents (BoltClient *client, BoltDevice *device, GCancellable *cancellable, GError **error) { g_autoptr(BoltDevice) dev = NULL; g_autoptr(GPtrArray) uuids = NULL; const char *parent; uuids = g_ptr_array_new_full (16, g_object_unref); dev = g_object_ref (device); while ((parent = bolt_device_get_parent (dev)) != NULL) { g_autofree char *up = g_strdup (parent); g_clear_object (&dev); /* 'parent' can be invalid */ parent = NULL; dev = bolt_client_get_device (client, up, NULL, error); if (dev == NULL) return NULL; g_ptr_array_add (uuids, g_object_ref (dev)); } return g_steal_pointer (&uuids); } BoltDevice * bolt_client_get_device (BoltClient *client, const char *uid, GCancellable *cancel, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GError) err = NULL; BoltDevice *dev = NULL; GDBusConnection *bus = NULL; const char *opath = NULL; g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), "DeviceByUid", g_variant_new ("(s)", uid), G_DBUS_CALL_FLAGS_NONE, -1, cancel, &err); if (val == NULL) { bolt_error_propagate_stripped (error, &err); return NULL; } bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); g_variant_get (val, "(&o)", &opath); if (opath == NULL) return NULL; dev = bolt_device_new_for_object_path (bus, opath, cancel, error); return dev; } BoltDevice * bolt_client_find_device (BoltClient *client, const char *name, GError **error) { g_autoptr(GPtrArray) devices = NULL; devices = bolt_client_list_devices (client, NULL, error); if (devices == NULL) return NULL; for (guint i = 0; i < devices->len; i++) { BoltDevice *dev = g_ptr_array_index (devices, i); if (bolt_streq (name, bolt_device_get_uid (dev)) || bolt_streq (name, bolt_device_get_name (dev))) return g_object_ref (dev); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "could not find device matching '%s'", name); return NULL; } BoltDevice * bolt_client_enroll_device (BoltClient *client, const char *uid, BoltPolicy policy, BoltAuthCtrl flags, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GError) err = NULL; g_autofree char *fstr = NULL; BoltDevice *dev = NULL; GDBusConnection *bus = NULL; GVariant *params = NULL; const char *opath = NULL; const char *pstr; g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, error); if (pstr == NULL) return NULL; fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, error); if (fstr == NULL) return NULL; params = g_variant_new ("(sss)", uid, pstr, fstr); val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), "EnrollDevice", params, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err); if (val == NULL) { bolt_error_propagate_stripped (error, &err); return NULL; } bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); g_variant_get (val, "(&o)", &opath); if (opath == NULL) return NULL; dev = bolt_device_new_for_object_path (bus, opath, NULL, error); return dev; } void bolt_client_enroll_device_async (BoltClient *client, const char *uid, BoltPolicy policy, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autofree char *fstr = NULL; GError *err = NULL; GVariant *params; const char *pstr; g_return_if_fail (BOLT_IS_CLIENT (client)); g_return_if_fail (uid != NULL); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback == NULL); pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err); if (pstr == NULL) { g_task_report_error (client, callback, user_data, NULL, err); return; } fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); if (fstr == NULL) { g_task_report_error (client, callback, user_data, NULL, err); return; } params = g_variant_new ("(sss)", uid, pstr, fstr); g_dbus_proxy_call (G_DBUS_PROXY (client), "EnrollDevice", params, G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback, user_data); } gboolean bolt_client_enroll_device_finish (BoltClient *client, GAsyncResult *res, char **path, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); val = g_dbus_proxy_call_finish (G_DBUS_PROXY (client), res, &err); if (val == NULL) { bolt_error_propagate_stripped (error, &err); return FALSE; } if (path != NULL) g_variant_get (val, "(o)", path); return TRUE; } typedef struct OpData { const char *iface; /* Manager or Device */ const char *method; /* Enroll or Authorize */ char *path; /* object path */ GVariant *params; /* parameters */ } OpData; static OpData * op_data_new_enroll (const char *uid, const char *policy, const char *flags) { GVariant *params; OpData *op; params = g_variant_new ("(sss)", uid, policy, flags); op = g_slice_new (OpData); op->iface = BOLT_DBUS_INTERFACE; op->method = "EnrollDevice"; op->params = g_variant_ref_sink (params); op->path = g_strdup (BOLT_DBUS_PATH); return op; } static OpData * op_data_new_authorize (const char *uid, const char *flags) { OpData *op = NULL; GVariant *params; char *path; path = bolt_gen_object_path (BOLT_DBUS_PATH_DEVICES, uid); params = g_variant_new ("(s)", flags); op = g_slice_new (OpData); op->iface = BOLT_DBUS_DEVICE_INTERFACE; op->method = "Authorize"; op->params = g_variant_ref_sink (params); op->path = path; /* takes ownership */ return op; } static void op_data_free (OpData *op) { g_clear_pointer (&op->params, g_variant_unref); g_clear_pointer (&op->path, g_free); g_slice_free (OpData, op); } static void op_queue_free (GQueue *queue) { g_queue_free_full (queue, (GDestroyNotify) op_data_free); } static void allop_one_done (GObject *source_object, GAsyncResult *res, gpointer user_data); static gboolean allop_continue (BoltClient *client, GTask *task, GQueue *ops) { GDBusConnection *bus; GCancellable *cancel; OpData *op; cancel = g_task_get_cancellable (task); bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); op = g_queue_pop_head (ops); if (op == NULL) return TRUE; g_dbus_connection_call (bus, BOLT_DBUS_NAME, op->path, op->iface, op->method, op->params, NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancel, allop_one_done, task); op_data_free (op); return FALSE; } static void allop_one_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GVariant) val = NULL; BoltClient *client; GDBusConnection *bus; gboolean done; GError *err = NULL; GQueue *ops; GTask *task; task = G_TASK (user_data); ops = g_task_get_task_data (task); client = g_task_get_source_object (task); bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); val = g_dbus_connection_call_finish (bus, res, &err); if (val == NULL) { g_task_return_error (task, err); /* takes ownership */ g_object_unref (task); /* we are done (albeit with an error) */ return; } done = allop_continue (client, task, ops); if (done) { /* we are done */ g_task_return_boolean (task, TRUE); g_object_unref (task); } } void bolt_client_enroll_all_async (BoltClient *client, GPtrArray *uuids, BoltPolicy policy, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autofree char *fstr = NULL; GError *err = NULL; const char *pstr; GQueue *ops; GTask *task; g_return_if_fail (BOLT_IS_CLIENT (client)); g_return_if_fail (uuids != NULL && uuids->len > 0); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err); if (pstr == NULL) { g_task_report_error (client, callback, user_data, NULL, err); return; } fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); if (fstr == NULL) { g_task_report_error (client, callback, user_data, NULL, err); return; } task = g_task_new (client, cancellable, callback, user_data); g_task_set_return_on_cancel (task, TRUE); ops = g_queue_new (); g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free); for (guint i = 0; i < uuids->len; i++) { const char *uid = g_ptr_array_index (uuids, i); OpData *op; op = op_data_new_enroll (uid, pstr, fstr); g_queue_push_tail (ops, op); } allop_continue (client, task, ops); } gboolean bolt_client_enroll_all_finish (BoltClient *client, GAsyncResult *res, GError **error) { g_autoptr(GError) err = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); g_return_val_if_fail (g_task_is_valid (res, client), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = g_task_propagate_boolean (G_TASK (res), &err); if (!ok) bolt_error_propagate_stripped (error, &err); return ok; } void bolt_client_authorize_all_async (BoltClient *client, GPtrArray *uuids, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autofree char *fstr = NULL; GError *err = NULL; GQueue *ops; GTask *task; g_return_if_fail (BOLT_IS_CLIENT (client)); g_return_if_fail (uuids != NULL && uuids->len > 0); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); if (fstr == NULL) { g_task_report_error (client, callback, user_data, NULL, err); return; } task = g_task_new (client, cancellable, callback, user_data); g_task_set_return_on_cancel (task, TRUE); ops = g_queue_new (); g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free); for (guint i = 0; i < uuids->len; i++) { const char *uid = g_ptr_array_index (uuids, i); OpData *op; op = op_data_new_authorize (uid, fstr); g_queue_push_tail (ops, op); } allop_continue (client, task, ops); } gboolean bolt_client_authorize_all_finish (BoltClient *client, GAsyncResult *res, GError **error) { g_autoptr(GError) err = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); g_return_val_if_fail (g_task_is_valid (res, client), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = g_task_propagate_boolean (G_TASK (res), &err); if (!ok) bolt_error_propagate_stripped (error, &err); return ok; } void bolt_client_connect_all_async (BoltClient *client, GPtrArray *devices, BoltPolicy policy, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autofree char *fstr = NULL; GError *err = NULL; const char *pstr; GQueue *ops; GTask *task; g_return_if_fail (BOLT_IS_CLIENT (client)); g_return_if_fail (devices != NULL && devices->len > 0); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err); if (pstr == NULL) { g_task_report_error (client, callback, user_data, NULL, err); return; } fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); if (fstr == NULL) { g_task_report_error (client, callback, user_data, NULL, err); return; } task = g_task_new (client, cancellable, callback, user_data); g_task_set_return_on_cancel (task, TRUE); ops = g_queue_new (); g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free); for (guint i = 0; i < devices->len; i++) { BoltDevice *dev = g_ptr_array_index (devices, i); const char *uid = bolt_device_get_uid (dev); OpData *op; if (bolt_device_is_stored (dev)) op = op_data_new_authorize (uid, fstr); else op = op_data_new_enroll (uid, pstr, fstr); g_queue_push_tail (ops, op); } allop_continue (client, task, ops); } gboolean bolt_client_connect_all_finish (BoltClient *client, GAsyncResult *res, GError **error) { g_autoptr(GError) err = NULL; gboolean ok; g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); g_return_val_if_fail (g_task_is_valid (res, client), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = g_task_propagate_boolean (G_TASK (res), &err); if (!ok) bolt_error_propagate_stripped (error, &err); return ok; } gboolean bolt_client_forget_device (BoltClient *client, const char *uid, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GError) err = NULL; g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), "ForgetDevice", g_variant_new ("(s)", uid), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err); if (val == NULL) { bolt_error_propagate_stripped (error, &err); return FALSE; } return TRUE; } void bolt_client_forget_device_async (BoltClient *client, const char *uid, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (BOLT_IS_CLIENT (client)); g_return_if_fail (uid == NULL); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback == NULL); g_dbus_proxy_call (G_DBUS_PROXY (client), "ForgetDevice", g_variant_new ("(s)", uid), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback, user_data); } gboolean bolt_client_forget_device_finish (BoltClient *client, GAsyncResult *res, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GError) err = NULL; g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); val = g_dbus_proxy_call_finish (G_DBUS_PROXY (client), res, &err); if (val == NULL) { bolt_error_propagate_stripped (error, &err); return FALSE; } return TRUE; } BoltPower * bolt_client_new_power_client (BoltClient *client, GCancellable *cancellable, GError **error) { GDBusConnection *bus = NULL; g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); return bolt_power_new_for_object_path (bus, cancellable, error); } /* getter */ guint bolt_client_get_version (BoltClient *client) { guint val = 0; g_return_val_if_fail (BOLT_IS_CLIENT (client), val); val = bolt_proxy_get_uint32_by_pspec (client, props[PROP_VERSION]); return val; } gboolean bolt_client_is_probing (BoltClient *client) { gboolean val = FALSE; g_return_val_if_fail (BOLT_IS_CLIENT (client), val); val = bolt_proxy_get_bool_by_pspec (client, props[PROP_PROBING]); return val; } BoltPolicy bolt_client_get_policy (BoltClient *client) { gint val = BOLT_POLICY_UNKNOWN; g_return_val_if_fail (BOLT_IS_CLIENT (client), val); val = bolt_proxy_get_enum_by_pspec (client, props[PROP_POLICY]); return val; } BoltSecurity bolt_client_get_security (BoltClient *client) { gint val = BOLT_SECURITY_UNKNOWN; g_return_val_if_fail (BOLT_IS_CLIENT (client), val); val = bolt_proxy_get_enum_by_pspec (client, props[PROP_SECURITY]); return val; } BoltAuthMode bolt_client_get_authmode (BoltClient *client) { guint val = BOLT_AUTH_DISABLED; g_return_val_if_fail (BOLT_IS_CLIENT (client), val); val = bolt_proxy_get_flags_by_pspec (client, props[PROP_AUTHMODE]); return val; } BoltPowerState bolt_client_get_power_state (BoltClient *client) { gint val = BOLT_FORCE_POWER_UNSET; g_return_val_if_fail (BOLT_IS_CLIENT (client), val); val = bolt_proxy_get_enum_by_pspec (client, props[PROP_POWERSTATE]); return val; } void bolt_client_set_authmode_async (BoltClient *client, BoltAuthMode mode, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_auto(GValue) val = G_VALUE_INIT; g_return_if_fail (BOLT_IS_CLIENT (client)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); g_value_init (&val, BOLT_TYPE_AUTH_MODE); g_value_set_flags (&val, mode); bolt_proxy_set_async (BOLT_PROXY (client), props[PROP_AUTHMODE], &val, cancellable, callback, user_data); } gboolean bolt_client_set_authmode_finish (BoltClient *client, GAsyncResult *res, GError **error) { g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); return bolt_proxy_set_property_finish (res, error); } /* utility functions */ static gint device_sort_by_syspath (gconstpointer ap, gconstpointer bp, gpointer data) { BoltDevice *a = BOLT_DEVICE (*((BoltDevice **) ap)); BoltDevice *b = BOLT_DEVICE (*((BoltDevice **) bp)); gint sort_order = GPOINTER_TO_INT (data); const char *pa; const char *pb; pa = bolt_device_get_syspath (a); pb = bolt_device_get_syspath (b); return sort_order * g_strcmp0 (pa, pb); } void bolt_devices_sort_by_syspath (GPtrArray *devices, gboolean reverse) { gpointer sort_order = GINT_TO_POINTER (reverse ? -1 : 1); if (devices == NULL) return; g_ptr_array_sort_with_data (devices, device_sort_by_syspath, sort_order); } bolt-0.9.2/cli/bolt-client.h000066400000000000000000000200241417453051000156140ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-device.h" #include "bolt-domain.h" #include "bolt-power.h" #include "bolt-proxy.h" G_BEGIN_DECLS #define BOLT_TYPE_CLIENT bolt_client_get_type () G_DECLARE_FINAL_TYPE (BoltClient, bolt_client, BOLT, CLIENT, BoltProxy); BoltClient * bolt_client_new (GError **error); void bolt_client_new_async (GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); BoltClient * bolt_client_new_finish (GAsyncResult *res, GError **error); GPtrArray * bolt_client_list_domains (BoltClient *client, GCancellable *cancellable, GError **error); BoltDomain * bolt_client_find_domain (BoltClient *client, const char *name, GError **error); GPtrArray * bolt_client_list_devices (BoltClient *client, GCancellable *cancellable, GError **error); GPtrArray * bolt_client_list_parents (BoltClient *client, BoltDevice *device, GCancellable *cancellable, GError **error); BoltDevice * bolt_client_get_device (BoltClient *client, const char *uid, GCancellable *cancellable, GError **error); BoltDevice * bolt_client_find_device (BoltClient *client, const char *name, GError **error); BoltDevice * bolt_client_enroll_device (BoltClient *client, const char *uid, BoltPolicy policy, BoltAuthCtrl flags, GError **error); void bolt_client_enroll_device_async (BoltClient *client, const char *uid, BoltPolicy policy, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_client_enroll_device_finish (BoltClient *client, GAsyncResult *res, char **path, GError **error); void bolt_client_enroll_all_async (BoltClient *client, GPtrArray *uuids, BoltPolicy policy, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_client_enroll_all_finish (BoltClient *client, GAsyncResult *res, GError **error); void bolt_client_authorize_all_async (BoltClient *client, GPtrArray *uuids, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_client_authorize_all_finish (BoltClient *client, GAsyncResult *res, GError **error); void bolt_client_connect_all_async (BoltClient *client, GPtrArray *devices, BoltPolicy policy, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_client_connect_all_finish (BoltClient *client, GAsyncResult *res, GError **error); gboolean bolt_client_forget_device (BoltClient *client, const char *uid, GError **error); void bolt_client_forget_device_async (BoltClient *client, const char *uid, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_client_forget_device_finish (BoltClient *client, GAsyncResult *res, GError **error); BoltPower * bolt_client_new_power_client (BoltClient *client, GCancellable *cancellable, GError **error); /* getter */ guint bolt_client_get_version (BoltClient *client); gboolean bolt_client_is_probing (BoltClient *client); BoltPolicy bolt_client_get_policy (BoltClient *client); BoltSecurity bolt_client_get_security (BoltClient *client); BoltAuthMode bolt_client_get_authmode (BoltClient *client); BoltPowerState bolt_client_get_power_state (BoltClient *client); /* setter */ void bolt_client_set_authmode_async (BoltClient *client, BoltAuthMode mode, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_client_set_authmode_finish (BoltClient *client, GAsyncResult *res, GError **error); /* utility functions */ void bolt_devices_sort_by_syspath (GPtrArray *devices, gboolean reverse); G_END_DECLS bolt-0.9.2/cli/bolt-device.c000066400000000000000000000374411417453051000156030ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-device.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-names.h" #include "bolt-wire.h" #include struct _BoltDevice { BoltProxy parent; }; enum { PROP_0, /* D-Bus Props */ PROP_UID, PROP_NAME, PROP_VENDOR, PROP_GEN, PROP_TYPE, PROP_STATUS, PROP_AUTHFLAGS, PROP_PARENT, PROP_SYSPATH, PROP_DOMAIN, PROP_CONNTIME, PROP_AUTHTIME, PROP_LINKSPEED, PROP_STORED, PROP_POLICY, PROP_KEY, PROP_STORETIME, PROP_LABEL, PROP_LAST }; static GParamSpec *props[PROP_LAST] = {NULL, }; G_DEFINE_TYPE (BoltDevice, bolt_device, BOLT_TYPE_PROXY); static void bolt_device_class_init (BoltDeviceClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bolt_proxy_property_getter; gobject_class->set_property = bolt_proxy_property_setter; props[PROP_UID] = g_param_spec_string ("uid", "Uid", "The unique identifier.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_NAME] = g_param_spec_string ("name", "Name", "Human readable device name.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_VENDOR] = g_param_spec_string ("vendor", "Vendor", "The name of the device vendor", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_GEN] = g_param_spec_uint ("generation", "Generation", "The generation of the controller chip.", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_TYPE] = g_param_spec_enum ("type", "Type", "The type, i.e. host or peripheral.", BOLT_TYPE_DEVICE_TYPE, BOLT_DEVICE_PERIPHERAL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_STATUS] = g_param_spec_enum ("status", "Status", "The device status.", BOLT_TYPE_STATUS, BOLT_STATUS_DISCONNECTED, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_AUTHFLAGS] = g_param_spec_flags ("authflags", "AuthFlags", "Flags describing the authentication state.", BOLT_TYPE_AUTH_FLAGS, BOLT_AUTH_NONE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_PARENT] = g_param_spec_string ("parent", "Parent", "Unique identifier of the parent.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_SYSPATH] = g_param_spec_string ("syspath", "SysfsPath", "The sysfs path of the udev device.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_DOMAIN] = g_param_spec_string ("domain", "Domain", "Unique id of the corresponding domain.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_CONNTIME] = g_param_spec_uint64 ("conntime", "ConnectTime", "When was the device connected?", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_AUTHTIME] = g_param_spec_uint64 ("authtime", "AuthorizeTime", "When was the device authorized?", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_LINKSPEED] = g_param_spec_boxed ("linkspeed", "LinkSpeed", "The speed to the parent", BOLT_TYPE_LINK_SPEED, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_STORED] = g_param_spec_boolean ("stored", "Stored", "Is the device recorded in the database?", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_POLICY] = g_param_spec_enum ("policy", "Policy", "What to do when the device is connected?", BOLT_TYPE_POLICY, BOLT_POLICY_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_KEY] = g_param_spec_enum ("key", "Key", "State of the device key.", BOLT_TYPE_KEY_STATE, BOLT_KEY_MISSING, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_STORETIME] = g_param_spec_uint64 ("storetime", "StoreTime", "When was the device stored?", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_LABEL] = g_param_spec_string ("label", "Label", "The name given by bolt or the user.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); } static void bolt_device_init (BoltDevice *mgr) { } /* public methods */ BoltDevice * bolt_device_new_for_object_path (GDBusConnection *bus, const char *path, GCancellable *cancel, GError **error) { g_autoptr(BoltDevice) dev = NULL; gboolean ok; g_return_val_if_fail (G_IS_DBUS_CONNECTION (bus), NULL); g_return_val_if_fail (path != NULL, NULL); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); dev = g_initable_new (BOLT_TYPE_DEVICE, cancel, error, "g-flags", G_DBUS_PROXY_FLAGS_NONE, "g-connection", bus, "g-name", BOLT_DBUS_NAME, "g-object-path", path, "g-interface-name", BOLT_DBUS_DEVICE_INTERFACE, NULL); if (dev == NULL) return NULL; ok = bolt_proxy_set_wireconv (BOLT_PROXY (dev), props[PROP_LINKSPEED], "linkspeed", bolt_link_speed_to_wire, bolt_link_speed_from_wire, error); if (!ok) return NULL; return g_steal_pointer (&dev); } gboolean bolt_device_authorize (BoltDevice *dev, BoltAuthCtrl flags, GCancellable *cancel, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *fstr = NULL; g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, error); if (fstr == NULL) return FALSE; g_dbus_proxy_call_sync (G_DBUS_PROXY (dev), "Authorize", g_variant_new ("(s)", fstr), G_DBUS_CALL_FLAGS_NONE, -1, cancel, &err); if (err != NULL) return bolt_error_propagate_stripped (error, &err); return TRUE; } void bolt_device_authorize_async (BoltDevice *dev, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GError *err = NULL; g_autofree char *fstr = NULL; g_return_if_fail (BOLT_IS_DEVICE (dev)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); if (fstr == NULL) { g_task_report_error (dev, callback, user_data, NULL, err); return; } g_dbus_proxy_call (G_DBUS_PROXY (dev), "Authorize", g_variant_new ("(s)", fstr), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback, user_data); } gboolean bolt_device_authorize_finish (BoltDevice *dev, GAsyncResult *res, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); val = g_dbus_proxy_call_finish (G_DBUS_PROXY (dev), res, &err); if (val == NULL) { bolt_error_propagate_stripped (error, &err); return FALSE; } return TRUE; } const char * bolt_device_get_uid (BoltDevice *dev) { const char *str; g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); str = bolt_proxy_get_string_by_pspec (dev, props[PROP_UID]); return str; } const char * bolt_device_get_name (BoltDevice *dev) { const char *str; g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); str = bolt_proxy_get_string_by_pspec (dev, props[PROP_NAME]); return str; } const char * bolt_device_get_vendor (BoltDevice *dev) { const char *str; g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); str = bolt_proxy_get_string_by_pspec (dev, props[PROP_VENDOR]); return str; } guint bolt_device_get_generation (BoltDevice *dev) { guint32 val = 0; g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); val = bolt_proxy_get_uint32_by_pspec (dev, props[PROP_GEN]); return (guint) val; } BoltDeviceType bolt_device_get_device_type (BoltDevice *dev) { gint val = BOLT_DEVICE_PERIPHERAL; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_enum_by_pspec (dev, props[PROP_TYPE]); return val; } gboolean bolt_device_is_host (BoltDevice *dev) { BoltDeviceType dt = bolt_device_get_device_type (dev); return dt == BOLT_DEVICE_HOST; } BoltStatus bolt_device_get_status (BoltDevice *dev) { gint val = BOLT_STATUS_UNKNOWN; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_enum_by_pspec (dev, props[PROP_STATUS]); return val; } BoltAuthFlags bolt_device_get_authflags (BoltDevice *dev) { guint val = BOLT_AUTH_NONE; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_flags_by_pspec (dev, props[PROP_AUTHFLAGS]); return val; } const char * bolt_device_get_parent (BoltDevice *dev) { const char *str; g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); str = bolt_proxy_get_string_by_pspec (dev, props[PROP_PARENT]); return str; } const char * bolt_device_get_syspath (BoltDevice *dev) { const char *str; g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); str = bolt_proxy_get_string_by_pspec (dev, props[PROP_SYSPATH]); return str; } const char * bolt_device_get_domain (BoltDevice *dev) { const char *str; g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); str = bolt_proxy_get_string_by_pspec (dev, props[PROP_DOMAIN]); return str; } guint64 bolt_device_get_conntime (BoltDevice *dev) { guint64 val = 0; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_uint64_by_pspec (dev, props[PROP_CONNTIME]); return val; } guint64 bolt_device_get_authtime (BoltDevice *dev) { guint64 val = 0; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_uint64_by_pspec (dev, props[PROP_AUTHTIME]); return val; } void bolt_device_get_linkspeed (BoltDevice *dev, BoltLinkSpeed *speed) { g_auto(GValue) value = G_VALUE_INIT; BoltLinkSpeed *ls; gboolean ok; g_return_if_fail (BOLT_IS_DEVICE (dev)); g_return_if_fail (speed != NULL); ok = bolt_proxy_get_dbus_property (BOLT_PROXY (dev), props[PROP_LINKSPEED], &value); if (!ok) return; ls = g_value_get_boxed (&value); *speed = *ls; } gboolean bolt_device_is_stored (BoltDevice *dev) { gboolean val = FALSE; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_bool_by_pspec (dev, props[PROP_STORED]); return val; } BoltPolicy bolt_device_get_policy (BoltDevice *dev) { gint val = BOLT_POLICY_DEFAULT; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_enum_by_pspec (dev, props[PROP_POLICY]); return val; } BoltKeyState bolt_device_get_keystate (BoltDevice *dev) { gint val = BOLT_KEY_MISSING; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_enum_by_pspec (dev, props[PROP_KEY]); return val; } guint64 bolt_device_get_storetime (BoltDevice *dev) { guint64 val = 0; g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); val = bolt_proxy_get_uint64_by_pspec (dev, props[PROP_STORETIME]); return val; } const char * bolt_device_get_label (BoltDevice *dev) { const char *str; g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); str = bolt_proxy_get_string_by_pspec (dev, props[PROP_LABEL]); return str; } char * bolt_device_get_display_name (BoltDevice *dev) { const char *label; const char *name; const char *vendor; label = bolt_device_get_label (dev); if (label != NULL) return g_strdup (label); name = bolt_device_get_name (dev); vendor = bolt_device_get_vendor (dev); return g_strdup_printf ("%s %s", vendor, name); } guint64 bolt_device_get_timestamp (BoltDevice *dev) { BoltStatus status; guint64 timestamp = 0; status = bolt_device_get_status (dev); switch (status) { case BOLT_STATUS_AUTHORIZING: case BOLT_STATUS_AUTH_ERROR: case BOLT_STATUS_CONNECTING: case BOLT_STATUS_CONNECTED: timestamp = bolt_device_get_conntime (dev); break; case BOLT_STATUS_DISCONNECTED: /* implicit: device is stored */ timestamp = bolt_device_get_storetime (dev); break; case BOLT_STATUS_AUTHORIZED: case BOLT_STATUS_AUTHORIZED_DPONLY: case BOLT_STATUS_AUTHORIZED_NEWKEY: case BOLT_STATUS_AUTHORIZED_SECURE: timestamp = bolt_device_get_authtime (dev); break; case BOLT_STATUS_UNKNOWN: timestamp = 0; break; } return timestamp; } bolt-0.9.2/cli/bolt-device.h000066400000000000000000000066651417453051000156140ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-proxy.h" #include "bolt-wire.h" G_BEGIN_DECLS #define BOLT_TYPE_DEVICE bolt_device_get_type () G_DECLARE_FINAL_TYPE (BoltDevice, bolt_device, BOLT, DEVICE, BoltProxy); BoltDevice * bolt_device_new_for_object_path (GDBusConnection *bus, const char *path, GCancellable *cancellable, GError **error); gboolean bolt_device_authorize (BoltDevice *dev, BoltAuthCtrl flags, GCancellable *cancellable, GError **error); void bolt_device_authorize_async (BoltDevice *dev, BoltAuthCtrl flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_device_authorize_finish (BoltDevice *dev, GAsyncResult *res, GError **error); /* getter */ const char * bolt_device_get_uid (BoltDevice *dev); const char * bolt_device_get_name (BoltDevice *dev); const char * bolt_device_get_vendor (BoltDevice *dev); guint bolt_device_get_generation (BoltDevice *dev); BoltDeviceType bolt_device_get_device_type (BoltDevice *dev); gboolean bolt_device_is_host (BoltDevice *dev); BoltStatus bolt_device_get_status (BoltDevice *dev); BoltAuthFlags bolt_device_get_authflags (BoltDevice *dev); const char * bolt_device_get_parent (BoltDevice *dev); const char * bolt_device_get_syspath (BoltDevice *dev); const char * bolt_device_get_domain (BoltDevice *dev); guint64 bolt_device_get_conntime (BoltDevice *dev); guint64 bolt_device_get_authtime (BoltDevice *dev); void bolt_device_get_linkspeed (BoltDevice *dev, BoltLinkSpeed *speed); gboolean bolt_device_is_stored (BoltDevice *dev); BoltPolicy bolt_device_get_policy (BoltDevice *dev); BoltKeyState bolt_device_get_keystate (BoltDevice *dev); guint64 bolt_device_get_storetime (BoltDevice *dev); const char * bolt_device_get_label (BoltDevice *dev); /* derived getter */ char * bolt_device_get_display_name (BoltDevice *dev); guint64 bolt_device_get_timestamp (BoltDevice *dev); G_END_DECLS bolt-0.9.2/cli/bolt-domain.c000066400000000000000000000127661417453051000156160ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-domain.h" #include struct _BoltDomain { BoltProxy parent; }; enum { PROP_0, /* D-Bus Props */ PROP_UID, PROP_ID, PROP_SYSPATH, PROP_SECURITY, PROP_BOOTACL, PROP_IOMMU, PROP_LAST }; static GParamSpec *props[PROP_LAST] = {NULL, }; G_DEFINE_TYPE (BoltDomain, bolt_domain, BOLT_TYPE_PROXY); static void bolt_domain_class_init (BoltDomainClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bolt_proxy_property_getter; gobject_class->set_property = bolt_proxy_property_setter; props[PROP_UID] = g_param_spec_string ("uid", "Uid", "The unique identifier.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_ID] = g_param_spec_string ("id", "Id", "The sysfs name.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_SYSPATH] = g_param_spec_string ("syspath", "SysfsPath", "Sysfs path of the udev device.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_SECURITY] = g_param_spec_enum ("security", "SecurityLevel", "The security level set in the BIOS.", BOLT_TYPE_SECURITY, BOLT_SECURITY_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_BOOTACL] = g_param_spec_boxed ("bootacl", "BootACL", "Pre-boot access control list (uuids).", G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_IOMMU] = g_param_spec_boolean ("iommu", "IOMMU", "Is IOMMU based DMA protection active?", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); } static void bolt_domain_init (BoltDomain *domain) { } /* public methods */ BoltDomain * bolt_domain_new_for_object_path (GDBusConnection *bus, const char *path, GCancellable *cancel, GError **error) { BoltDomain *dom; g_return_val_if_fail (G_IS_DBUS_CONNECTION (bus), NULL); g_return_val_if_fail (path != NULL, NULL); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); dom = g_initable_new (BOLT_TYPE_DOMAIN, cancel, error, "g-flags", G_DBUS_PROXY_FLAGS_NONE, "g-connection", bus, "g-name", BOLT_DBUS_NAME, "g-object-path", path, "g-interface-name", BOLT_DBUS_DOMAIN_INTERFACE, NULL); return dom; } const char * bolt_domain_get_uid (BoltDomain *domain) { const char *str; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); str = bolt_proxy_get_string_by_pspec (domain, props[PROP_UID]); return str; } const char * bolt_domain_get_id (BoltDomain *domain) { const char *str; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); str = bolt_proxy_get_string_by_pspec (domain, props[PROP_ID]); return str; } const char * bolt_domain_get_syspath (BoltDomain *domain) { const char *str; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); str = bolt_proxy_get_string_by_pspec (domain, props[PROP_SYSPATH]); return str; } BoltSecurity bolt_domain_get_security (BoltDomain *domain) { gint val = BOLT_SECURITY_UNKNOWN; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), val); val = bolt_proxy_get_enum_by_pspec (domain, props[PROP_SECURITY]); return val; } char ** bolt_domain_get_bootacl (BoltDomain *domain) { char **strv; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), NULL); strv = bolt_proxy_get_strv_by_pspec (domain, props[PROP_BOOTACL]); return strv; } gboolean bolt_domain_is_online (BoltDomain *domain) { const char *syspath; syspath = bolt_domain_get_syspath (domain); return syspath != NULL; } gboolean bolt_domain_has_iommu (BoltDomain *domain) { gboolean val = FALSE; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), val); val = bolt_proxy_get_bool_by_pspec (domain, props[PROP_IOMMU]); return val; } bolt-0.9.2/cli/bolt-domain.h000066400000000000000000000033331417453051000156110ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-proxy.h" G_BEGIN_DECLS #define BOLT_TYPE_DOMAIN bolt_domain_get_type () G_DECLARE_FINAL_TYPE (BoltDomain, bolt_domain, BOLT, DOMAIN, BoltProxy); BoltDomain * bolt_domain_new_for_object_path (GDBusConnection *bus, const char *path, GCancellable *cancellable, GError **error); /* getter */ const char * bolt_domain_get_uid (BoltDomain *domain); const char * bolt_domain_get_id (BoltDomain *domain); const char * bolt_domain_get_syspath (BoltDomain *domain); BoltSecurity bolt_domain_get_security (BoltDomain *domain); char ** bolt_domain_get_bootacl (BoltDomain *domain); gboolean bolt_domain_is_online (BoltDomain *domain); gboolean bolt_domain_has_iommu (BoltDomain *domain); G_END_DECLS bolt-0.9.2/cli/bolt-power.c000066400000000000000000000146421417453051000154760ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-error.h" #include "bolt-power.h" #include #include struct _BoltPower { BoltProxy parent; }; enum { PROP_0, /* D-Bus Props */ PROP_SUPPORTED, PROP_STATE, PROP_TIMEOUT, PROP_LAST }; static GParamSpec *props[PROP_LAST] = {NULL, }; G_DEFINE_TYPE (BoltPower, bolt_power, BOLT_TYPE_PROXY); static void bolt_power_class_init (BoltPowerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bolt_proxy_property_getter; gobject_class->set_property = bolt_proxy_property_setter; props[PROP_SUPPORTED] = g_param_spec_boolean ("supported", "Supported", "Is forcing power supported?", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_STATE] = g_param_spec_enum ("state", "State", "Current force power state.", BOLT_TYPE_POWER_STATE, BOLT_FORCE_POWER_UNSET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); props[PROP_TIMEOUT] = g_param_spec_uint ("timeout", "Timeout", "Force power timeout.", 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, props); } static void bolt_power_init (BoltPower *power) { } /* public methods */ BoltPower * bolt_power_new_for_object_path (GDBusConnection *bus, GCancellable *cancel, GError **error) { BoltPower *pwr; g_return_val_if_fail (G_IS_DBUS_CONNECTION (bus), NULL); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); pwr = g_initable_new (BOLT_TYPE_POWER, cancel, error, "g-flags", G_DBUS_PROXY_FLAGS_NONE, "g-connection", bus, "g-name", BOLT_DBUS_NAME, "g-object-path", BOLT_DBUS_PATH, "g-interface-name", BOLT_DBUS_POWER_INTERFACE, NULL); return pwr; } int bolt_power_force_power (BoltPower *power, GError **error) { g_autoptr(GUnixFDList) fds = NULL; g_autoptr(GVariant) val = NULL; g_autoptr(GError) err = NULL; GVariant *input; int fd = -1; g_return_val_if_fail (BOLT_IS_POWER (power), -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); input = g_variant_new ("(ss)", "boltctl", /* who */ ""); /* flags */ val = g_dbus_proxy_call_with_unix_fd_list_sync (G_DBUS_PROXY (power), "ForcePower", input, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &fds, NULL, &err); if (val == NULL) { bolt_error_propagate_stripped (error, &err); return -1; } if (g_unix_fd_list_get_length (fds) != 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid number of file descriptors returned: %d", g_unix_fd_list_get_length (fds)); return -1; } fd = g_unix_fd_list_get (fds, 0, NULL); return fd; } GPtrArray * bolt_power_list_guards (BoltPower *power, GCancellable *cancel, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GVariantIter) iter = NULL; GPtrArray *res; const char *id; const char *who; guint pid; g_return_val_if_fail (BOLT_IS_POWER (power), NULL); g_return_val_if_fail (!cancel || G_IS_CANCELLABLE (cancel), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); val = g_dbus_proxy_call_sync (G_DBUS_PROXY (power), "ListGuards", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancel, error); if (val == NULL) return NULL; res = g_ptr_array_new_with_free_func ((GDestroyNotify) bolt_power_guard_free); g_variant_get (val, "(a(ssu))", &iter); while (g_variant_iter_loop (iter, "(ssu)", &id, &who, &pid)) { BoltPowerGuard *g = g_new (BoltPowerGuard, 1); g->id = g_strdup (id); g->who = g_strdup (who); g->pid = pid; g_ptr_array_add (res, g); } return res; } /* getter */ gboolean bolt_power_is_supported (BoltPower *power) { gboolean val; g_return_val_if_fail (BOLT_IS_POWER (power), FALSE); val = bolt_proxy_get_bool_by_pspec (power, props[PROP_SUPPORTED]); return val; } BoltPowerState bolt_power_get_state (BoltPower *power) { gint val = BOLT_FORCE_POWER_UNSET; g_return_val_if_fail (BOLT_IS_POWER (power), val); val = bolt_proxy_get_enum_by_pspec (power, props[PROP_STATE]); return val; } /* bolt power guard functions */ void bolt_power_guard_free (BoltPowerGuard *guard) { g_return_if_fail (guard != NULL); g_free (guard->id); g_free (guard->who); g_free (guard); } bolt-0.9.2/cli/bolt-power.h000066400000000000000000000035041417453051000154760ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-enums.h" #include "bolt-proxy.h" G_BEGIN_DECLS #define BOLT_TYPE_POWER bolt_power_get_type () G_DECLARE_FINAL_TYPE (BoltPower, bolt_power, BOLT, POWER, BoltProxy); BoltPower * bolt_power_new_for_object_path (GDBusConnection *bus, GCancellable *cancellable, GError **error); /* methods */ int bolt_power_force_power (BoltPower *power, GError **error); GPtrArray * bolt_power_list_guards (BoltPower *power, GCancellable *cancellable, GError **error); /* getter */ gboolean bolt_power_is_supported (BoltPower *power); BoltPowerState bolt_power_get_state (BoltPower *power); /* */ typedef struct BoltPowerGuard_ { char *id; char *who; guint pid; } BoltPowerGuard; void bolt_power_guard_free (BoltPowerGuard *guard); G_END_DECLS bolt-0.9.2/cli/bolt-proxy.c000066400000000000000000000526551417453051000155310ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "bolt-proxy.h" #include "bolt-dbus.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-glue.h" #include "bolt-names.h" #include "bolt-str.h" static void bolt_proxy_handle_props_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data); static void bolt_proxy_handle_dbus_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *params, gpointer user_data); enum { PROP_0, PROP_BUS, /* d-bus names */ PROP_OBJECT_PATH, PROP_IFACE_NAME, PROP_LAST }; struct _BoltProxyClassPrivate { GHashTable *wire_convs; }; static gpointer bolt_proxy_parent_class = NULL; static void bolt_proxy_init (GTypeInstance *, gpointer g_class); static void bolt_proxy_class_init (BoltProxyClass *klass); static void bolt_proxy_base_init (gpointer g_class); static void bolt_proxy_base_finalize (gpointer g_class); GType bolt_proxy_get_type (void) { static volatile gsize proxy_type = 0; if (g_once_init_enter (&proxy_type)) { GType type_id; const GTypeInfo type_info = { sizeof (BoltProxyClass), bolt_proxy_base_init, (GBaseFinalizeFunc) bolt_proxy_base_finalize, (GClassInitFunc) bolt_proxy_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (BoltProxy), 0, /* n_preallocs */ bolt_proxy_init, NULL, /* value_table */ }; type_id = g_type_register_static (G_TYPE_DBUS_PROXY, "BoltProxy", &type_info, G_TYPE_FLAG_ABSTRACT); g_type_add_class_private (type_id, sizeof (BoltProxyClassPrivate)); g_once_init_leave (&proxy_type, type_id); } return proxy_type; } static void bolt_proxy_constructed (GObject *object) { g_autoptr(GError) err = NULL; GDBusInterfaceInfo *info; const char *interface; G_OBJECT_CLASS (bolt_proxy_parent_class)->constructed (object); g_signal_connect (object, "g-properties-changed", G_CALLBACK (bolt_proxy_handle_props_changed), object); g_signal_connect (object, "g-signal", G_CALLBACK (bolt_proxy_handle_dbus_signal), object); if (g_dbus_proxy_get_interface_info (G_DBUS_PROXY (object)) != NULL) return; interface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (object)); if (interface == NULL) return; bolt_dbus_ensure_resources (); info = bolt_dbus_interface_info_lookup (BOLT_DBUS_GRESOURCE_PATH, interface, &err); if (info == NULL) { g_warning ("could not load interface info: %s", err->message); return; } g_dbus_proxy_set_interface_info (G_DBUS_PROXY (object), info); g_dbus_interface_info_unref (info); } static const BoltProxySignal * bolt_proxy_get_dbus_signals (guint *n) { *n = 0; return NULL; } static void bolt_proxy_class_init (BoltProxyClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); bolt_proxy_parent_class = g_type_class_peek_parent (klass); gobject_class->constructed = bolt_proxy_constructed; klass->get_dbus_signals = bolt_proxy_get_dbus_signals; } static void bolt_proxy_base_init (gpointer g_class) { BoltProxyClass *klass = g_class; klass->priv = G_TYPE_CLASS_GET_PRIVATE (g_class, BOLT_TYPE_PROXY, BoltProxyClassPrivate); memset (klass->priv, 0, sizeof (BoltProxyClassPrivate)); klass->priv->wire_convs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) bolt_wire_conv_unref); } static void bolt_proxy_base_finalize (gpointer g_class) { BoltProxyClass *klass = g_class; BoltProxyClassPrivate *priv = klass->priv; g_hash_table_unref (priv->wire_convs); } static void bolt_proxy_init (GTypeInstance *instance, gpointer g_class) { } static void bolt_proxy_handle_props_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data) { g_autoptr(GVariantIter) iter = NULL; gboolean handled; GParamSpec **pp; const char *key; guint n; pp = g_object_class_list_properties (G_OBJECT_GET_CLASS (proxy), &n); g_variant_get (changed_properties, "a{sv}", &iter); while (g_variant_iter_next (iter, "{&sv}", &key, NULL)) { handled = FALSE; for (guint i = 0; !handled && i < n; i++) { GParamSpec *pspec = pp[i]; const char *nick; const char *name; nick = g_param_spec_get_nick (pspec); name = g_param_spec_get_name (pspec); handled = bolt_streq (nick, key); if (handled) g_object_notify (G_OBJECT (user_data), name); } } g_free (pp); } static void bolt_proxy_handle_dbus_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *params, gpointer user_data) { const BoltProxySignal *ps; guint n; if (signal_name == NULL) return; ps = BOLT_PROXY_GET_CLASS (proxy)->get_dbus_signals (&n); for (guint i = 0; i < n; i++) { const BoltProxySignal *sig = &ps[i]; if (g_str_equal (sig->theirs, signal_name)) { sig->handle (G_OBJECT (proxy), proxy, params); break; } } } static BoltWireConv * bolt_proxy_get_wire_conv (BoltProxy *proxy, GParamSpec *spec, GError **error) { GDBusInterfaceInfo *info; GDBusPropertyInfo *pi; BoltProxyClass *klass; const char *nick; BoltWireConv *conv; klass = BOLT_PROXY_GET_CLASS (proxy); nick = g_param_spec_get_nick (spec); conv = g_hash_table_lookup (klass->priv->wire_convs, nick); if (conv) return conv; info = g_dbus_proxy_get_interface_info (G_DBUS_PROXY (proxy)); if (info == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not find dbus interface info"); return NULL; } pi = g_dbus_interface_info_lookup_property (info, nick); if (pi == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "could not find dbus property info"); return NULL; } conv = bolt_wire_conv_for (G_VARIANT_TYPE (pi->signature), spec); if (conv == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "could create conversion helper"); return NULL; } /* nick is valid as long as spec is valid and * a reference to spec is held by conv */ g_hash_table_insert (klass->priv->wire_convs, (gpointer) nick, conv); return conv; } /* public methods */ gboolean bolt_proxy_set_wireconv (BoltProxy *proxy, GParamSpec *param_spec, const char *custom_id, BoltConvToWire to_wire, BoltConvFromWire from_wire, GError **error) { GDBusInterfaceInfo *info; GDBusPropertyInfo *pi; BoltProxyClass *klass; const char *nick; BoltWireConv *conv; klass = BOLT_PROXY_GET_CLASS (proxy); nick = g_param_spec_get_nick (param_spec); conv = g_hash_table_lookup (klass->priv->wire_convs, nick); if (conv) return TRUE; info = g_dbus_proxy_get_interface_info (G_DBUS_PROXY (proxy)); if (info == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not find dbus interface info"); return FALSE; } pi = g_dbus_interface_info_lookup_property (info, nick); if (pi == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "could not find dbus property info"); return FALSE; } conv = bolt_wire_conv_custom (G_VARIANT_TYPE (pi->signature), param_spec, custom_id, to_wire, from_wire); if (conv == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "could create conversion helper"); return FALSE; } /* nick is valid as long as spec is valid and * a reference to spec is held by conv */ g_hash_table_insert (klass->priv->wire_convs, (gpointer) nick, conv); return TRUE; } void bolt_proxy_property_getter (GObject *object, guint prop_id, GValue *value, GParamSpec *spec) { bolt_proxy_get_dbus_property (BOLT_PROXY (object), spec, value); } gboolean bolt_proxy_get_dbus_property (BoltProxy *proxy, GParamSpec *spec, GValue *value) { g_autoptr(GVariant) val = NULL; g_autoptr(GError) err = NULL; BoltWireConv *conv; const char *nick; gboolean ok; nick = g_param_spec_get_nick (spec); val = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), nick); if (val == NULL) { const GValue *def = g_param_spec_get_default_value (spec); if (G_VALUE_TYPE (value) == 0) g_value_init (value, spec->value_type); g_value_copy (def, value); g_warning ("Unknown property: %s (%s)", spec->name, nick); return FALSE; } conv = bolt_proxy_get_wire_conv (proxy, spec, &err); if (conv == NULL) { g_warning ("No conversion available for dbus property '%s': %s", nick, err->message); return FALSE; } ok = bolt_wire_conv_from_wire (conv, val, value, &err); if (!ok) g_warning ("Failed to convert dbus property '%s': %s", nick, err->message); return ok; } const char * bolt_proxy_get_object_path (BoltProxy *proxy) { return g_dbus_proxy_get_object_path (G_DBUS_PROXY (proxy)); } gboolean bolt_proxy_has_name_owner (BoltProxy *proxy) { const char *name_owner; g_return_val_if_fail (proxy != NULL, FALSE); g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)); return name_owner != NULL; } static GParamSpec * find_property (BoltProxy *proxy, const char *name, GError **error) { GParamSpec *res = NULL; GParamSpec **pp; guint n; pp = g_object_class_list_properties (G_OBJECT_GET_CLASS (proxy), &n); for (guint i = 0; i < n; i++) { GParamSpec *pspec = pp[i]; if (bolt_streq (pspec->name, name)) { res = pspec; break; } } if (pp == NULL) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "could not find property '%s'", name); g_free (pp); return res; } gboolean bolt_proxy_get_bool_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); return g_value_get_boolean (&val); } gint bolt_proxy_get_enum_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = (BoltProxy *) object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); return g_value_get_enum (&val); } guint bolt_proxy_get_flags_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = (BoltProxy *) object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); return g_value_get_flags (&val); } guint32 bolt_proxy_get_uint32_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = (BoltProxy *) object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); return g_value_get_uint (&val); } gint64 bolt_proxy_get_int64_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = (BoltProxy *) object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); return g_value_get_int64 (&val); } guint64 bolt_proxy_get_uint64_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = (BoltProxy *) object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); return g_value_get_uint64 (&val); } const char * bolt_proxy_get_string_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = (BoltProxy *) object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); /* NB: Yes, this is return the string inside the value, * although the value is freed. This therefore makes * the assumption that the GValue's value was initialized * via a static string (g_value_set_static_string). Ergo, * this must always be true for bolt_proxy_get_dbus_property */ return g_value_get_string (&val); } char ** bolt_proxy_get_strv_by_pspec (gpointer object, GParamSpec *spec) { g_auto(GValue) val = G_VALUE_INIT; BoltProxy *proxy = (BoltProxy *) object; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); bolt_proxy_get_dbus_property (proxy, spec, &val); return (char **) g_value_dup_boxed (&val); } gboolean bolt_proxy_set_property (BoltProxy *proxy, const char *name, GVariant *value, GCancellable *cancellable, GError **error) { GParamSpec *pp; const char *iface; gboolean ok = FALSE; GVariant *res; pp = find_property (proxy, name, NULL); if (pp != NULL) name = g_param_spec_get_nick (pp); iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy)); res = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", iface, name, value), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (res) { g_variant_unref (res); ok = TRUE; } return ok; } void bolt_proxy_set_property_async (BoltProxy *proxy, const char *name, GVariant *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GParamSpec *pp; const char *iface; pp = find_property (proxy, name, NULL); if (pp != NULL) name = g_param_spec_get_nick (pp); iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy)); g_dbus_proxy_call (G_DBUS_PROXY (proxy), "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", iface, name, value), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback, user_data); } gboolean bolt_proxy_set_property_finish (GAsyncResult *res, GError **error) { BoltProxy *proxy; GVariant *val = NULL; proxy = (BoltProxy *) g_async_result_get_source_object (res); val = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); if (val == NULL) return FALSE; g_variant_unref (val); return TRUE; } gboolean bolt_proxy_set (BoltProxy *proxy, GParamSpec *spec, const GValue *value, GCancellable *cancellable, GError **error) { g_autoptr(GVariant) val = NULL; g_autoptr(GVariant) res = NULL; BoltWireConv *conv; const char *name; const char *iface; g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); g_return_val_if_fail (G_IS_PARAM_SPEC (spec), FALSE); g_return_val_if_fail (G_IS_VALUE (value), FALSE); g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); conv = bolt_proxy_get_wire_conv (proxy, spec, error); if (conv == NULL) return FALSE; val = bolt_wire_conv_to_wire (conv, value, error); if (val == NULL) return FALSE; name = g_param_spec_get_nick (spec); iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy)); res = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", iface, name, val), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); return res != NULL; } void bolt_proxy_set_async (BoltProxy *proxy, GParamSpec *spec, const GValue *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GVariant) val = NULL; GError *err; BoltWireConv *conv; const char *name; const char *iface; g_return_if_fail (BOLT_IS_PROXY (proxy)); g_return_if_fail (G_IS_PARAM_SPEC (spec)); g_return_if_fail (G_IS_VALUE (value)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); conv = bolt_proxy_get_wire_conv (proxy, spec, &err); if (conv != NULL) val = bolt_wire_conv_to_wire (conv, value, &err); if (val == NULL) { g_task_report_error (proxy, callback, user_data, NULL, err); return; } name = g_param_spec_get_nick (spec); iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy)); g_dbus_proxy_call (G_DBUS_PROXY (proxy), "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", iface, name, val), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback, user_data); } gboolean bolt_proxy_set_finish (GAsyncResult *res, GError **error) { g_autoptr(GVariant) val = NULL; BoltProxy *proxy; proxy = (BoltProxy *) g_async_result_get_source_object (res); val = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); return val != NULL; } static void set_async_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(GParamSpec) spec = user_data; gboolean ok; ok = bolt_proxy_set_finish (res, &err); if (!ok) g_warning ("Error setting property '%s': %s", spec->name, err->message); } void bolt_proxy_property_setter (GObject *object, guint prop_id, const GValue *value, GParamSpec *spec) { g_return_if_fail (BOLT_IS_PROXY (object)); g_return_if_fail (value != NULL); g_return_if_fail (G_IS_PARAM_SPEC (spec)); bolt_proxy_set_async (BOLT_PROXY (object), spec, value, NULL, set_async_done, g_param_spec_ref (spec)); } bolt-0.9.2/cli/bolt-proxy.h000066400000000000000000000130441417453051000155230ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include G_BEGIN_DECLS typedef struct BoltProxySignal { const char *theirs; void (*handle)(GObject *self, GDBusProxy *bus_proxy, GVariant *params); } BoltProxySignal; #define BOLT_TYPE_PROXY (bolt_proxy_get_type ()) G_DECLARE_DERIVABLE_TYPE (BoltProxy, bolt_proxy, BOLT, PROXY, GDBusProxy) typedef struct _BoltProxyClassPrivate BoltProxyClassPrivate; struct _BoltProxyClass { GDBusProxyClass parent; /*< private >*/ BoltProxyClassPrivate *priv; /*< public >*/ /* virtuals */ const BoltProxySignal * (*get_dbus_signals) (guint *n); /* for the future */ gpointer padding[10]; }; gboolean bolt_proxy_set_wireconv (BoltProxy *proxy, GParamSpec *param_spec, const char *custom_id, BoltConvToWire to_wire, BoltConvFromWire from_wire, GError **error); void bolt_proxy_property_getter (GObject *object, guint prop_id, GValue *value, GParamSpec *spec); gboolean bolt_proxy_get_dbus_property (BoltProxy *proxy, GParamSpec *spec, GValue *value); gboolean bolt_proxy_has_name_owner (BoltProxy *proxy); const char * bolt_proxy_get_object_path (BoltProxy *proxy) G_DEPRECATED_FOR (g_dbus_proxy_get_object_path); gboolean bolt_proxy_get_bool_by_pspec (gpointer proxy, GParamSpec *spec); gint bolt_proxy_get_enum_by_pspec (gpointer proxy, GParamSpec *spec); guint bolt_proxy_get_flags_by_pspec (gpointer proxy, GParamSpec *spec); guint32 bolt_proxy_get_uint32_by_pspec (gpointer proxy, GParamSpec *spec); gint64 bolt_proxy_get_int64_by_pspec (gpointer proxy, GParamSpec *spec); guint64 bolt_proxy_get_uint64_by_pspec (gpointer proxy, GParamSpec *spec); const char * bolt_proxy_get_string_by_pspec (gpointer proxy, GParamSpec *spec); char ** bolt_proxy_get_strv_by_pspec (gpointer proxy, GParamSpec *spec); gboolean bolt_proxy_set_property (BoltProxy *proxy, const char *name, GVariant *value, GCancellable *cancellable, GError **error); void bolt_proxy_set_property_async (BoltProxy *proxy, const char *name, GVariant *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_proxy_set_property_finish (GAsyncResult *res, GError **error); gboolean bolt_proxy_set (BoltProxy *proxy, GParamSpec *spec, const GValue *value, GCancellable *cancellable, GError **error); void bolt_proxy_set_async (BoltProxy *proxy, GParamSpec *spec, const GValue *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean bolt_proxy_set_finish (GAsyncResult *res, GError **error); void bolt_proxy_property_setter (GObject *object, guint prop_id, const GValue *value, GParamSpec *spec); G_END_DECLS bolt-0.9.2/cli/boltctl-authorize.c000066400000000000000000000113411417453051000170500ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "bolt-error.h" #include "bolt-str.h" #include typedef struct AuthorizeAllData { GMainLoop *loop; gboolean done; gboolean res; GError **error; } AuthorizeAllData; static void authorize_all_done (GObject *source, GAsyncResult *res, gpointer user_data) { AuthorizeAllData *data = (AuthorizeAllData *) user_data; gboolean ok; ok = bolt_client_authorize_all_finish (BOLT_CLIENT (source), res, data->error); data->res = ok; data->done = TRUE; g_main_loop_quit (data->loop); } static gboolean authorize_all (BoltClient *client, const char *uid, BoltAuthCtrl flags, GError **error) { g_autoptr(BoltDevice) target = NULL; g_autoptr(GPtrArray) parents = NULL; g_autoptr(GPtrArray) uuids = NULL; g_autoptr(GMainLoop) loop = NULL; g_autoptr(GError) err = NULL; AuthorizeAllData data; target = bolt_client_get_device (client, uid, NULL, &err); if (target == NULL) { g_printerr ("Could not look up target: %s\n", err->message); return EXIT_FAILURE; } parents = bolt_client_list_parents (client, target, NULL, &err); if (parents == NULL) { g_printerr ("Could not look up parents: %s\n", err->message); return EXIT_FAILURE; } uuids = g_ptr_array_new_full (parents->len, NULL); /* reverse order, starting from the root! */ for (guint i = parents->len; i > 0; i--) { BoltDevice *dev = g_ptr_array_index (parents, i - 1); const char *id = bolt_device_get_uid (dev); BoltStatus status = bolt_device_get_status (dev); if (!bolt_status_is_pending (status)) continue; g_ptr_array_add (uuids, (gpointer) id); } /* the target itself */ g_ptr_array_add (uuids, (gpointer) uid); loop = g_main_loop_new (NULL, FALSE); data.loop = loop; data.done = FALSE; data.error = error; bolt_client_authorize_all_async (client, uuids, flags, NULL, authorize_all_done, &data); if (data.done == FALSE) g_main_loop_run (loop); return data.res; } int authorize (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(BoltDevice) dev = NULL; g_autoptr(GError) error = NULL; BoltAuthCtrl flags = BOLT_AUTHCTRL_NONE; BoltStatus status; const char *uid; gboolean ok; gboolean first_time = FALSE; gboolean chain_arg = FALSE; GOptionEntry options[] = { { "first-time", 'F', 0, G_OPTION_ARG_NONE, &first_time, "Fail if device is already authorized", NULL }, { "chain", 0, 0, G_OPTION_ARG_NONE, &chain_arg, "Authorize parent devices if necessary" }, { NULL } }; optctx = g_option_context_new ("DEVICE - Authorize a device"); g_option_context_add_main_entries (optctx, options, NULL); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); if (argc < 2) return usage_error_need_arg ("DEVICE"); uid = argv[1]; dev = bolt_client_get_device (client, uid, NULL, &error); if (dev == NULL) { g_printerr ("%s\n", error->message); return EXIT_FAILURE; } status = bolt_device_get_status (dev); if (chain_arg) ok = authorize_all (client, uid, flags, &error); else ok = bolt_device_authorize (dev, flags, NULL, &error); if (!ok) { if (bolt_err_badstate (error) && bolt_status_is_authorized (status) && !first_time) ok = TRUE; else g_printerr ("Authorization error: %s\n", error->message); } return ok ? EXIT_SUCCESS : EXIT_FAILURE; } bolt-0.9.2/cli/boltctl-cmds.h000066400000000000000000000035201417453051000157710ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "boltctl.h" G_BEGIN_DECLS typedef int (*BoltctlCommand) (BoltClient *client, int argc, char **argv); int authorize (BoltClient *client, int argc, char **argv); int config (BoltClient *client, int argc, char **argv); int enroll (BoltClient *client, int argc, char **argv); int forget (BoltClient *client, int argc, char **argv); int info (BoltClient *client, int argc, char **argv); int list_devices (BoltClient *client, int argc, char **argv); int list_domains (BoltClient *client, int argc, char **argv); int monitor (BoltClient *client, int argc, char **argv); int power (BoltClient *client, int argc, char **argv); G_END_DECLS bolt-0.9.2/cli/boltctl-config.c000066400000000000000000000213571417453051000163130ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl.h" #include "boltctl-cmds.h" #include "bolt-error.h" #include "bolt-glue.h" #include "bolt-str.h" #include #include static gboolean type_for_name (const char *name, GType *type, GError **error) { if (bolt_strcaseeq (name, "global") || bolt_strcaseeq (name, "daemon")) { *type = BOLT_TYPE_CLIENT; return TRUE; } if (bolt_strcaseeq (name, "domain")) { *type = BOLT_TYPE_DOMAIN; return TRUE; } else if (bolt_strcaseeq (name, "device")) { *type = BOLT_TYPE_DEVICE; return TRUE; } g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "unknown class '%s'", name); return FALSE; } static const char * name_for_type (GType type) { if (type == BOLT_TYPE_CLIENT) return "global"; else if (type == BOLT_TYPE_DOMAIN) return "domain"; else if (type == BOLT_TYPE_DEVICE) return "device"; else return "unknown"; } static BoltProxy * target_for_type (BoltClient *client, GType type, const char *name, GError **error) { BoltProxy *target = NULL; if (type == BOLT_TYPE_CLIENT) target = g_object_ref (BOLT_PROXY (client)); else if (type == BOLT_TYPE_DOMAIN) target = (BoltProxy *) bolt_client_find_domain (client, name, error); else if (type == BOLT_TYPE_DEVICE) target = (BoltProxy *) bolt_client_find_device (client, name, error); else g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "'%s' not handled", name); return target; } static int property_get (BoltProxy *proxy, GParamSpec *spec, GError **error) { g_auto(GValue) prop_val = G_VALUE_INIT; g_auto(GValue) str_val = G_VALUE_INIT; g_autofree char *val = NULL; gboolean ok; g_value_init (&prop_val, G_PARAM_SPEC_VALUE_TYPE (spec)); g_value_init (&str_val, G_TYPE_STRING); ok = bolt_proxy_get_dbus_property (proxy, spec, &prop_val); if (!ok) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "Could not get property"); return EXIT_FAILURE; } if (G_IS_PARAM_SPEC_ENUM (spec)) val = g_strdup (bolt_enum_to_string (spec->value_type, g_value_get_enum (&prop_val), error)); else if (G_IS_PARAM_SPEC_FLAGS (spec)) val = bolt_flags_to_string (spec->value_type, g_value_get_flags (&prop_val), error); else if (G_IS_PARAM_SPEC_BOOLEAN (spec)) val = g_strdup (bolt_yesno (g_value_get_boolean (&prop_val))); else if (g_value_transform (&prop_val, &str_val)) val = g_value_dup_string (&str_val); else val = g_strdup_value_contents (&prop_val); if (val == NULL) return EXIT_FAILURE; g_print ("%s\n", val); return EXIT_SUCCESS; } static int property_set (BoltProxy *proxy, GParamSpec *spec, const char *str, GError **error) { g_auto(GValue) val = G_VALUE_INIT; gboolean ok; ok = bolt_str_parse_by_pspec (spec, str, &val, error); if (!ok) return EXIT_FAILURE; ok = bolt_proxy_set (proxy, spec, &val, NULL, error); return ok ? EXIT_SUCCESS : EXIT_FAILURE; } /* describe */ static void describe_properties (GType type) { g_autoptr(GPtrArray) props = NULL; const char *prefix = name_for_type (type); props = bolt_properties_for_type (type); for (guint i = 0; i < props->len; i++) { GParamSpec *spec = g_ptr_array_index (props, i); const char *desc = g_param_spec_get_blurb (spec); gboolean can_write = spec->flags & G_PARAM_WRITABLE; const char *rw = can_write ? "rw" : "r-"; g_print ("%s %s.%-14s %s\n", rw, prefix, spec->name, desc); } } static int do_describe (BoltClient *client, int argc, char **argv) { g_autoptr(GError) err = NULL; gboolean ok; ok = check_argc (argc, 0, 1, &err); if (!ok) return usage_error (err); if (argc == 1) /* boltctl config --describe */ { describe_properties (BOLT_TYPE_CLIENT); describe_properties (BOLT_TYPE_DOMAIN); describe_properties (BOLT_TYPE_DEVICE); } else if (argc == 2) /* boltctl config --describe {global,device,domain} */ { GType type; ok = type_for_name (argv[1], &type, &err); if (!ok) return usage_error (err); describe_properties (type); } return 0; } static gboolean parse_option (const char *opt_str, GType *type_out, char **prop_out, GError **error) { g_auto(GStrv) comps = NULL; GType type = 0; gboolean ok; guint n; comps = g_strsplit (opt_str, ".", 2); n = g_strv_length (comps); *prop_out = NULL; /* Determine the fine structure of OPTION, i.e. is it * type.property or property */ switch (n) { case 1: type = BOLT_TYPE_CLIENT; *prop_out = g_steal_pointer (&comps[0]); ok = TRUE; break; case 2: ok = type_for_name (comps[0], &type, error); *prop_out = g_steal_pointer (&comps[1]); break; default: g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "invalid OPTION string '%s'", opt_str); ok = FALSE; break; } *type_out = type; return ok; } /* */ typedef enum { ACTION_GET = 1, ACTION_SET = 2, } Action; static const char *summary = "Describing available items: \n" " config --describe [global|domain|device]\n" "\n" "Getting items:\n" " config KEY\n" " config .KEY TARGET\n" "\n" "Setting items:\n" " config KEY VALUE\n" " config .KEY TARGET VALUE\n"; int config (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(GPtrArray) props = NULL; g_autoptr(BoltProxy) target = NULL; g_autoptr(GError) err = NULL; g_autofree char *pstr = NULL; const char *value = NULL; gboolean describe = FALSE; gboolean ok = FALSE; GParamSpec *prop; Action action; GType type = 0; int res = EXIT_FAILURE; GOptionEntry options[] = { { "describe", 'd', 0, G_OPTION_ARG_NONE, &describe, "Describe options", NULL }, { NULL } }; optctx = g_option_context_new ("- Inspect and modify options"); g_option_context_add_main_entries (optctx, options, NULL); g_option_context_set_summary (optctx, summary); g_option_context_set_strict_posix (optctx, TRUE); if (!g_option_context_parse (optctx, &argc, &argv, &err)) return usage_error (err); if (describe) return do_describe (client, argc, argv); /* get or set */ ok = check_argc (argc, 1, 3, &err); if (!ok) return usage_error (err); ok = parse_option (argv[1], &type, &pstr, &err); if (!ok) return usage_error (err); props = bolt_properties_for_type (type); ok = bolt_properties_find (props, pstr, &prop, &err); if (!ok) return usage_error (err); if (argc == 2) { /* boltctl config */ target = g_object_ref (BOLT_PROXY (client)); action = ACTION_GET; } else if (argc == 3 && type != BOLT_TYPE_CLIENT) { /* boltctl config <{device, domain}.property> */ target = target_for_type (client, type, argv[2], &err); action = ACTION_GET; } else if (argc == 3 && type == BOLT_TYPE_CLIENT) { /* boltctl config */ target = g_object_ref (BOLT_PROXY (client)); action = ACTION_SET; value = argv[2]; } else if (argc == 4) { /* boltctl config <{device, domain}.property> */ target = target_for_type (client, type, argv[2], &err); action = ACTION_SET; value = argv[3]; } if (target == NULL) return usage_error (err); /* must be set if target == NULL */ /* action must either be GET or SET */ if (action == ACTION_GET) res = property_get (target, prop, &err); else if (action == ACTION_SET) res = property_set (target, prop, value, &err); if (res == EXIT_FAILURE) report_error (NULL, err); return res; } bolt-0.9.2/cli/boltctl-domains.c000066400000000000000000000103661417453051000164760ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "boltctl-uidfmt.h" #include "bolt-str.h" #include /* domain related commands */ static void print_domain (BoltDomain *domain, gboolean verbose) { g_auto(GStrv) bootacl = NULL; const char *tree_branch; const char *tree_right; const char *tree_cont; const char *tree_space; const char *uid; const char *name; const char *syspath; const char *security; BoltSecurity sl; gboolean online; gboolean iommu; tree_branch = bolt_glyph (TREE_BRANCH); tree_right = bolt_glyph (TREE_RIGHT); tree_cont = bolt_glyph (TREE_VERTICAL); tree_space = bolt_glyph (TREE_SPACE); uid = bolt_domain_get_uid (domain); name = bolt_domain_get_id (domain); sl = bolt_domain_get_security (domain); syspath = bolt_domain_get_syspath (domain); security = bolt_security_to_string (sl); bootacl = bolt_domain_get_bootacl (domain); online = syspath != NULL; if (online) { g_print (" %s%s%s ", bolt_color (ANSI_GREEN), bolt_glyph (BLACK_CIRCLE), bolt_color (ANSI_NORMAL)); } else { g_print (" %s ", bolt_glyph (WHITE_CIRCLE)); } g_print ("%s %s", (name ? : "domain"), format_uid (uid)); g_print ("\n"); if (verbose) g_print (" %s online: %s\n", tree_branch, bolt_yesno (online)); if (verbose && syspath != NULL) g_print (" %s syspath: %s\n", tree_branch, syspath); if (bootacl) { guint acl_max = g_strv_length ((char **) bootacl); guint used = 0; guint i; for (i = 0; i < acl_max; i++) if (!bolt_strzero (bootacl[i])) used++; g_print (" %s bootacl: %u/%u\n", tree_branch, used, acl_max); for (i = 0; i < acl_max; i++) { const char *tree_sym = used > 1 ? tree_branch : tree_right; if (bolt_strzero (bootacl[i])) continue; g_print (" %s %s[%u]", tree_cont, tree_sym, i); g_print (" %s\n", format_uid (bootacl[i])); used--; } } iommu = bolt_domain_has_iommu (domain); g_print (" %s security: ", tree_right); if (bolt_security_is_interactive (sl) && iommu) g_print ("iommu+%s", security); else if (bolt_security_allows_pcie (sl) && iommu) g_print ("iommu"); else g_print ("%s", security); g_print ("\n"); if (verbose) { g_print (" %s %s iommu: %s\n", tree_space, tree_branch, bolt_yesno (iommu)); g_print (" %s %s level: %s\n", tree_space, tree_right, security); } g_print ("\n"); } int list_domains (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(GError) err = NULL; g_autoptr(GPtrArray) domains = NULL; gboolean details = FALSE; GOptionEntry options[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &details, "Show more details", NULL }, { NULL } }; optctx = g_option_context_new ("- List thunderbolt domains"); g_option_context_add_main_entries (optctx, options, NULL); if (!g_option_context_parse (optctx, &argc, &argv, &err)) return usage_error (err); domains = bolt_client_list_domains (client, NULL, &err); if (domains == NULL) { g_warning ("Could not list domains: %s", err->message); domains = g_ptr_array_new_with_free_func (g_object_unref); } for (guint i = 0; i < domains->len; i++) { BoltDomain *dom = g_ptr_array_index (domains, i); print_domain (dom, details); } return EXIT_SUCCESS; } bolt-0.9.2/cli/boltctl-enroll.c000066400000000000000000000112341417453051000163320ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "bolt-str.h" #include typedef struct EnrollAllData { GMainLoop *loop; gboolean done; gboolean res; } EnrollAllData; static void enroll_all_done (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) err = NULL; EnrollAllData *data = (EnrollAllData *) user_data; gboolean ok; ok = bolt_client_enroll_all_finish (BOLT_CLIENT (source), res, &err); if (!ok) g_printerr ("Could not enroll all devices: %s\n", err->message); data->done = TRUE; data->res = ok; g_main_loop_quit (data->loop); } static int enroll_all (BoltClient *client, const char *uid, BoltPolicy policy, BoltAuthCtrl flags) { g_autoptr(BoltDevice) target = NULL; g_autoptr(GPtrArray) parents = NULL; g_autoptr(GPtrArray) uuids = NULL; g_autoptr(GMainLoop) loop = NULL; g_autoptr(GError) err = NULL; EnrollAllData data; target = bolt_client_get_device (client, uid, NULL, &err); if (target == NULL) { g_printerr ("Could not look up target: %s\n", err->message); return EXIT_FAILURE; } parents = bolt_client_list_parents (client, target, NULL, &err); if (parents == NULL) { g_printerr ("Could not look up parents: %s\n", err->message); return EXIT_FAILURE; } uuids = g_ptr_array_new_full (parents->len, NULL); /* reverse order, starting from the root! */ for (guint i = parents->len; i > 0; i--) { BoltDevice *dev = g_ptr_array_index (parents, i - 1); const char *id = bolt_device_get_uid (dev); if (bolt_device_is_stored (dev) || bolt_device_is_host (dev)) continue; g_ptr_array_add (uuids, (gpointer) id); } /* the target itself */ g_ptr_array_add (uuids, (gpointer) uid); loop = g_main_loop_new (NULL, FALSE); data.loop = loop; data.done = FALSE; bolt_client_enroll_all_async (client, uuids, policy, flags, NULL, enroll_all_done, &data); if (data.done == FALSE) g_main_loop_run (loop); return data.res ? EXIT_SUCCESS : EXIT_FAILURE; } int enroll (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(BoltDevice) dev = NULL; g_autoptr(GError) error = NULL; const char *uid; BoltPolicy policy = BOLT_POLICY_DEFAULT; BoltAuthCtrl flags = BOLT_AUTHCTRL_NONE; const char *policy_arg = "default"; gboolean chain_arg = FALSE; GOptionEntry options[] = { { "policy", 0, 0, G_OPTION_ARG_STRING, &policy_arg, "Policy for the device; one of {auto, manual, *default}", "POLICY" }, { "chain", 0, 0, G_OPTION_ARG_NONE, &chain_arg, "Authorize parent devices if necessary" }, { NULL } }; optctx = g_option_context_new ("DEVICE - Authorize and store a device in the database"); g_option_context_add_main_entries (optctx, options, NULL); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); if (argc < 2) return usage_error_need_arg ("DEVICE"); policy = bolt_policy_from_string (policy_arg); if (!bolt_policy_validate (policy)) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid policy '%s'", policy_arg); return usage_error (error); } uid = argv[1]; if (chain_arg) return enroll_all (client, uid, policy, flags); dev = bolt_client_enroll_device (client, uid, policy, flags, &error); if (dev == NULL) { g_printerr ("%s\n", error->message); return EXIT_FAILURE; } print_device (dev, TRUE); return EXIT_SUCCESS; } bolt-0.9.2/cli/boltctl-forget.c000066400000000000000000000050771417453051000163350ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "bolt-str.h" #include int forget (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(GError) error = NULL; const char *uid; gboolean ok, forget_all = FALSE; GOptionEntry options[] = { { "all", 'a', 0, G_OPTION_ARG_NONE, &forget_all, "Forget all devices", NULL }, { NULL } }; optctx = g_option_context_new ("DEVICE - Remove a device from the store"); g_option_context_add_main_entries (optctx, options, NULL); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); if (forget_all) { g_autoptr(GPtrArray) devices = NULL; ok = check_argc (argc, 0, 0, &error); if (!ok) return usage_error (error); devices = bolt_client_list_devices (client, NULL, &error); if (devices == NULL) { g_printerr ("Failed to list devices: %s", error->message); return EXIT_FAILURE; } for (guint i = 0; i < devices->len; i++) { BoltDevice *dev = g_ptr_array_index (devices, i); if (!bolt_device_is_stored (dev)) continue; uid = bolt_device_get_uid (dev); ok = bolt_client_forget_device (client, uid, &error); if (!ok) { g_printerr ("Failed to forget device: %s\n", error->message); return EXIT_FAILURE; } } } else { if (argc < 2) return usage_error_need_arg ("DEVICE"); uid = argv[1]; ok = bolt_client_forget_device (client, uid, &error); if (!ok) { g_printerr ("Failed to forget device: %s\n", error->message); return EXIT_FAILURE; } } return EXIT_SUCCESS; } bolt-0.9.2/cli/boltctl-info.c000066400000000000000000000027711417453051000160000ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "bolt-str.h" #include int info (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(BoltDevice) dev = NULL; g_autoptr(GError) error = NULL; const char *uid; optctx = g_option_context_new ("DEVICE - Show information about a device"); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); if (argc < 2) return usage_error_need_arg ("DEVICE"); uid = argv[1]; dev = bolt_client_get_device (client, uid, NULL, &error); if (dev == NULL) { g_printerr ("%s\n", error->message); return EXIT_FAILURE; } print_device (dev, TRUE); return EXIT_SUCCESS; } bolt-0.9.2/cli/boltctl-list.c000066400000000000000000000037751417453051000160250ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "bolt-str.h" #include int list_devices (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; gboolean show_all = FALSE; GOptionEntry options[] = { { "all", 'a', 0, G_OPTION_ARG_NONE, &show_all, "Show all devices", NULL }, { NULL } }; optctx = g_option_context_new ("- List thunderbolt devices"); g_option_context_add_main_entries (optctx, options, NULL); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); devices = bolt_client_list_devices (client, NULL, &error); if (devices == NULL) { g_printerr ("Failed to list devices: %s", error->message); return EXIT_FAILURE; } bolt_devices_sort_by_syspath (devices, FALSE); for (guint i = 0; i < devices->len; i++) { BoltDevice *dev = g_ptr_array_index (devices, i); BoltDeviceType type; if (!show_all) { type = bolt_device_get_device_type (dev); if (type != BOLT_DEVICE_PERIPHERAL) continue; } print_device (dev, FALSE); } return EXIT_SUCCESS; } bolt-0.9.2/cli/boltctl-monitor.c000066400000000000000000000200661417453051000165310ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "bolt-str.h" #include static void handle_domain_added (BoltClient *cli, const char *opath, gpointer user_data) { g_autoptr(GError) err = NULL; GDBusConnection *bus; BoltDomain *dom; GPtrArray *domains = user_data; bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (cli)); dom = bolt_domain_new_for_object_path (bus, opath, NULL, &err); if (err != NULL) { g_warning ("Could not create proxy object for %s", opath); return; } g_print (" DomainAdded: %s\n", opath); g_ptr_array_add (domains, dom); } static void handle_domain_removed (BoltClient *cli, const char *opath, gpointer user_data) { GPtrArray *domains = user_data; BoltDomain *domain = NULL; for (guint i = 0; i < domains->len; i++) { BoltDomain *dom = g_ptr_array_index (domains, i); const char *dom_opath = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dom)); if (bolt_streq (opath, dom_opath)) { domain = dom; break; } } if (domain == NULL) { g_warning ("DomainRemoved signal for unknown domain: %s", opath); return; } g_print (" DomainRemoved: %s\n", opath); g_ptr_array_remove_fast (domains, domain); } static void handle_device_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data) { const char *uid = NULL; const char *dev_name = NULL; g_autofree char *val = NULL; BoltDevice *dev = BOLT_DEVICE (gobject); const char *prop_name; GValue prop_val = G_VALUE_INIT; GValue str_val = G_VALUE_INIT; uid = bolt_device_get_uid (dev); dev_name = bolt_device_get_name (dev); prop_name = g_param_spec_get_name (pspec); g_value_init (&prop_val, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_value_init (&str_val, G_TYPE_STRING); g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); g_object_get_property (G_OBJECT (dev), prop_name, &prop_val); g_print ("[%s] %30s | %10s -> ", uid, dev_name, prop_name); if (g_value_transform (&prop_val, &str_val)) val = g_value_dup_string (&str_val); else val = g_strdup_value_contents (&prop_val); g_print ("%s\n", val ? : ""); g_value_unset (&str_val); g_value_unset (&prop_val); } static void handle_device_added (BoltClient *cli, const char *opath, gpointer user_data) { g_autoptr(GError) err = NULL; GDBusConnection *bus; BoltDevice *dev; GPtrArray *devices = user_data; const char *name; const char *uid; bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (cli)); dev = bolt_device_new_for_object_path (bus, opath, NULL, &err); if (err != NULL) { g_warning ("Could not create proxy object for %s", opath); return; } uid = bolt_device_get_uid (dev); name = bolt_device_get_name (dev); g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); g_print ("[%s] %30s | DeviceAdded @ %s\n", uid, name, opath); g_ptr_array_add (devices, dev); g_signal_connect (dev, "notify", G_CALLBACK (handle_device_changed), NULL); } static void handle_device_removed (BoltClient *cli, const char *opath, gpointer user_data) { GPtrArray *devices = user_data; BoltDevice *device = NULL; const char *name; const char *uid; for (guint i = 0; i < devices->len; i++) { BoltDevice *dev = g_ptr_array_index (devices, i); const char *dev_opath = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev)); if (bolt_streq (opath, dev_opath)) { device = dev; break; } } if (device == NULL) { g_warning ("DeviceRemoved signal for unknown device: %s", opath); return; } uid = bolt_device_get_uid (device); name = bolt_device_get_name (device); g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); g_print ("[%s] %30s | DeviceRemoved @ %s\n", uid, name, opath); g_signal_handlers_block_by_func (device, handle_device_changed, devices); g_ptr_array_remove_fast (devices, device); } static void handle_probing_changed (BoltClient *client, GParamSpec *pspec, gpointer user_data) { gboolean probing = bolt_client_is_probing (client); g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); if (probing) g_print ("Probing started\n"); else g_print ("Probing done\n"); } int monitor (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(GError) error = NULL; g_autoptr(GMainLoop) main_loop = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) domains = NULL; g_autofree char *amstr = NULL; BoltSecurity security; BoltAuthMode authmode; guint version = 0; optctx = g_option_context_new ("- Watch for changes"); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); version = bolt_client_get_version (client); security = bolt_client_get_security (client); authmode = bolt_client_get_authmode (client); amstr = bolt_flags_to_string (BOLT_TYPE_AUTH_MODE, authmode, NULL); if (!bolt_proxy_has_name_owner (BOLT_PROXY (client))) g_print ("%s no name owner for bolt (not running?)\n", bolt_glyph (WARNING_SIGN)); g_print ("Bolt Version : %d.%d\n", VERSION_MAJOR, VERSION_MINOR); g_print ("Daemon API : %u\n", version); g_print ("Client API : %u\n", BOLT_DBUS_API_VERSION); g_print ("Security Level: %s\n", bolt_security_to_string (security)); g_print ("Auth Mode : %s\n", amstr); g_print ("Ready\n"); /* domains */ domains = bolt_client_list_domains (client, NULL, &error); if (domains == NULL) { g_warning ("Could not list domains: %s", error->message); domains = g_ptr_array_new_with_free_func (g_object_unref); g_clear_error (&error); } g_signal_connect (client, "domain-added", G_CALLBACK (handle_domain_added), domains); g_signal_connect (client, "domain-removed", G_CALLBACK (handle_domain_removed), domains); /* devices */ devices = bolt_client_list_devices (client, NULL, &error); if (devices == NULL) { g_warning ("Could not list devices: %s", error->message); devices = g_ptr_array_new_with_free_func (g_object_unref); g_clear_error (&error); } bolt_devices_sort_by_syspath (devices, FALSE); for (guint i = 0; i < devices->len; i++) { BoltDevice *dev = g_ptr_array_index (devices, i); g_signal_connect (dev, "notify", G_CALLBACK (handle_device_changed), NULL); } g_signal_connect (client, "device-added", G_CALLBACK (handle_device_added), devices); g_signal_connect (client, "device-removed", G_CALLBACK (handle_device_removed), devices); g_signal_connect (client, "notify::probing", G_CALLBACK (handle_probing_changed), NULL); main_loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (main_loop); g_signal_handlers_disconnect_by_func (client, G_CALLBACK (handle_probing_changed), NULL); return EXIT_SUCCESS; } bolt-0.9.2/cli/boltctl-power.c000066400000000000000000000071151417453051000161760ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "bolt-str.h" #include static gboolean quit_main_loop (gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); return FALSE; } int power (BoltClient *client, int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(GMainLoop) main_loop = NULL; g_autoptr(BoltPower) power = NULL; g_autoptr(GError) error = NULL; BoltPowerState state; gboolean do_query = FALSE; int fd; double timeout = 0; GOptionEntry options[] = { { "query", 'q', 0, G_OPTION_ARG_NONE, &do_query, "Query the status", NULL }, { "timeout", 't', 0, G_OPTION_ARG_DOUBLE, &timeout, "Quit after N seconds", NULL }, { NULL } }; optctx = g_option_context_new ("- Force power configuration"); g_option_context_add_main_entries (optctx, options, NULL); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); power = bolt_client_new_power_client (client, NULL, &error); if (power == NULL) { g_warning ("Could get proxy for power interface: %s", error->message); return EXIT_FAILURE; } if (do_query) { g_autoptr(GPtrArray) guards = NULL; const char *tree_branch; const char *tree_right; const char *str; gboolean supported; supported = bolt_power_is_supported (power); g_print ("supported: %s\n", bolt_yesno (supported)); if (!supported) return EXIT_SUCCESS; state = bolt_power_get_state (power); str = bolt_power_state_to_string (state); g_print ("power state: %s\n", str); guards = bolt_power_list_guards (power, NULL, &error); if (guards == NULL) { g_warning ("Could not list guards: %s", error->message); return EXIT_FAILURE; } tree_branch = bolt_glyph (TREE_BRANCH); tree_right = bolt_glyph (TREE_RIGHT); g_print ("%u active power guards%s\n", guards->len, guards->len > 0 ? ":" : ""); for (guint i = 0; i < guards->len; i++) { BoltPowerGuard *g = g_ptr_array_index (guards, 0); g_print (" guard '%s'\n", g->id); g_print (" %s who: %s\n", tree_branch, g->who); g_print (" %s pid: %u\n", tree_right, g->pid); g_print ("\n"); } return EXIT_SUCCESS; } fd = bolt_power_force_power (power, &error); if (fd == -1) { g_warning ("Could force power controller: %s", error->message); return EXIT_SUCCESS; } g_print ("acquired power guard (%d)\n", fd); main_loop = g_main_loop_new (NULL, FALSE); if (timeout > 0.) { guint tout = (guint) timeout * 1000; g_timeout_add (tout, quit_main_loop, main_loop); } g_main_loop_run (main_loop); (void) close (fd); return EXIT_SUCCESS; } bolt-0.9.2/cli/boltctl-uidfmt.c000066400000000000000000000112731417453051000163320ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-str.h" #include "bolt-term.h" #include "boltctl-uidfmt.h" #include #include #include char * bolt_uuid_format (const char *uuid, const char *salt, int fmt) { g_autoptr(GChecksum) chk = NULL; const char *tmp = NULL; guint op; int len; char *res; if (uuid == NULL) return NULL; g_return_val_if_fail (fmt > -1, NULL); op = fmt & 0xFF; switch (op) { case BOLT_UID_FORMAT_FULL: res = g_strdup (uuid); break; case BOLT_UID_FORMAT_SHORT: res = g_strdup_printf ("%.13s%s", uuid, bolt_glyph (ELLIPSIS)); break; case BOLT_UID_FORMAT_ALIAS: chk = g_checksum_new (G_CHECKSUM_SHA1); if (salt) g_checksum_update (chk, (const guchar *) salt, -1); g_checksum_update (chk, (const guchar *) uuid, -1); tmp = g_checksum_get_string (chk); res = g_strdup_printf ("%.8s-%.4s-%.4s-%.4s-%.12s", tmp, tmp + 8, tmp + 12, tmp + 16, tmp + 20); break; case BOLT_UID_FORMAT_LEN: len = MIN (fmt >> 8, 36); res = g_strdup_printf ("%.*s%s", len, uuid, (len < 36 ? bolt_glyph (ELLIPSIS) : "")); break; default: res = NULL; g_warning ("unknown uuid format enum value: %d", fmt); } return res; } int bolt_uuid_format_from_string (const char *str, GError **error) { char *end; gint64 val; if (bolt_streq (str, "short")) return BOLT_UID_FORMAT_SHORT; else if (bolt_streq (str, "full")) return BOLT_UID_FORMAT_FULL; else if (bolt_streq (str, "alias")) return BOLT_UID_FORMAT_ALIAS; val = g_ascii_strtoll (str, &end, 10); if (str == end) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "uuid format: unknown style: %s", str); return -1; } else if (val < 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "uuid format: invalid number: %s", str); return -1; } else if (val > 36) { return BOLT_UID_FORMAT_FULL; } return BOLT_UID_FORMAT_LEN | (val << 8); } /* */ int uuids_format; GHashTable *uuids_table; gboolean uuids_cleanup; char *uuids_salt; static void format_uid_cleanup (void) { g_clear_pointer (&uuids_table, g_hash_table_unref); g_clear_pointer (&uuids_salt, g_free); uuids_format = 0; } #define MACHINE_ID_PATH "/etc/machine-id" #define BOOT_ID_PATH "/proc/sys/kernel/random/boot_id" static char * get_salt (void) { char *salt = NULL; gboolean ok; ok = g_file_get_contents (MACHINE_ID_PATH, &salt, NULL, NULL); if (ok && !bolt_strzero (salt)) { g_debug ("using machine-id as salt"); return salt; } ok = g_file_get_contents (BOOT_ID_PATH, &salt, NULL, NULL); if (ok && !bolt_strzero (salt)) { g_debug ("using boot-id as salt"); return salt; } g_debug ("using PACKAGE_VERSION as pseudo-salt :("); return g_strdup (PACKAGE_VERSION); } int format_uid_init (const char *str, GError **error) { int fmt; g_return_val_if_fail (str != NULL, -1); fmt = bolt_uuid_format_from_string (str, error); if (fmt < 0) return fmt; uuids_format = fmt; if (uuids_table) g_hash_table_unref (uuids_table); uuids_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); uuids_salt = get_salt (); if (!uuids_cleanup) { uuids_cleanup = TRUE; atexit (format_uid_cleanup); } return fmt; } const char * format_uid (const char *uid) { const char *res; char *key, *val; if (uid == NULL) uid = ""; res = g_hash_table_lookup (uuids_table, uid); if (res != NULL) return res; key = g_strdup (uid); val = bolt_uuid_format (uid, uuids_salt, uuids_format); g_hash_table_insert (uuids_table, key, val); return val; } bolt-0.9.2/cli/boltctl-uidfmt.h000066400000000000000000000027021417453051000163340ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS /* how to format uuids */ typedef enum { BOLT_UID_FORMAT_FULL = 0, BOLT_UID_FORMAT_SHORT = 1, BOLT_UID_FORMAT_ALIAS = 2, BOLT_UID_FORMAT_LEN = 0xFF, } BoltUuidFormat; /* generic function */ char * bolt_uuid_format (const char *uuid, const char *salt, int fmt); int bolt_uuid_format_from_string (const char *str, GError **error); /* */ int format_uid_init (const char *str, GError **error); const char * format_uid (const char *uid); G_END_DECLS bolt-0.9.2/cli/boltctl.c000066400000000000000000000327051417453051000150470ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "boltctl-cmds.h" #include "boltctl-uidfmt.h" #include "bolt-client.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-macros.h" #include "bolt-str.h" #include "bolt-term.h" #include "bolt-time.h" #include #include #include gboolean check_argc (int argc, int lower, int upper, GError **error) { argc--; /* discard argv[0] */ if (lower == upper && argc != upper) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "unexpected number of arguments: %d, wanted %d", argc, upper); return FALSE; } else if (argc < lower) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "not enough arguments: %d, wanted at least %d", argc, lower); return FALSE; } else if (argc > upper) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "too many arguments: %d, wanted at most %d", argc, lower); return FALSE; } return TRUE; } int usage_error (GError *error) { g_printerr ("%s:", g_get_application_name ()); g_printerr ("%s error: %s", bolt_color (ANSI_RED), bolt_color (ANSI_NORMAL)); if (error) g_printerr ("%s", error->message); g_printerr ("\n"); g_printerr ("Try \"%s --help\" for more information.", g_get_prgname ()); g_printerr ("\n"); return EXIT_FAILURE; } int usage_error_need_arg (const char *arg) { g_autoptr(GError) error = NULL; g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "missing argument '%s'", arg); return usage_error (error); } int usage_error_too_many_args (void) { g_autoptr(GError) error = NULL; g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "too many arguments"); return usage_error (error); } int report_error (const char *prefix, GError *error) { g_printerr ("%s:", g_get_application_name ()); g_printerr ("%s error: %s", bolt_color (ANSI_RED), bolt_color (ANSI_NORMAL)); if (prefix) g_printerr ("%s", prefix); if (error) { if (g_dbus_error_is_remote_error (error)) g_dbus_error_strip_remote_error (error); if (prefix) g_printerr (": "); g_printerr ("%s", error->message); } g_printerr ("\n"); return EXIT_FAILURE; } /* device related commands */ static gboolean format_timestamp (BoltDevice *dev, char *buffer, gsize n, const char *timesel) { g_autofree char *str = NULL; guint64 ts; g_object_get (dev, timesel, &ts, NULL); if (ts > 0) { str = bolt_epoch_format (ts, "%c"); g_utf8_strncpy (buffer, str, n); } else { g_utf8_strncpy (buffer, "no", n); } return ts > 0; } static gboolean format_generation (guint generation, char *buffer, size_t n) { switch (generation) { case 1: case 2: case 3: g_snprintf (buffer, n, "Thunderbolt %u", generation); return TRUE; case 4: g_snprintf (buffer, n, "USB4"); return TRUE; case 0: g_snprintf (buffer, n, "Unknown"); return FALSE; default: g_snprintf (buffer, n, "%u", generation); return TRUE; } } void print_device (BoltDevice *dev, gboolean verbose) { g_autofree char *label = NULL; const char *path; const char *uid; const char *name; const char *vendor; const char *syspath; const char *parent; BoltDeviceType type; BoltStatus status; BoltAuthFlags aflags; BoltKeyState keystate; BoltPolicy policy; const char *status_color; const char *status_symbol; const char *type_text; const char *status_text; const char *tree_branch; const char *tree_right; const char *tree_space; gboolean stored; gboolean pcie; char buf[256]; guint gen; path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev)); uid = bolt_device_get_uid (dev); name = bolt_device_get_name (dev); vendor = bolt_device_get_vendor (dev); gen = bolt_device_get_generation (dev); type = bolt_device_get_device_type (dev); status = bolt_device_get_status (dev); aflags = bolt_device_get_authflags (dev); parent = bolt_device_get_parent (dev); syspath = bolt_device_get_syspath (dev); stored = bolt_device_is_stored (dev); policy = bolt_device_get_policy (dev); keystate = bolt_device_get_keystate (dev); pcie = bolt_flag_isclear (aflags, BOLT_AUTH_NOPCIE); status_symbol = bolt_glyph (BLACK_CIRCLE); tree_branch = bolt_glyph (TREE_BRANCH); tree_right = bolt_glyph (TREE_RIGHT); tree_space = bolt_glyph (TREE_SPACE); switch (status) { case BOLT_STATUS_DISCONNECTED: status_symbol = bolt_glyph (WHITE_CIRCLE); status_color = bolt_color (ANSI_NORMAL); status_text = "disconnected"; break; case BOLT_STATUS_CONNECTING: status_color = bolt_color (ANSI_YELLOW); status_text = "connecting"; break; case BOLT_STATUS_CONNECTED: status_color = bolt_color (ANSI_YELLOW); status_text = "connected"; break; case BOLT_STATUS_AUTHORIZED: case BOLT_STATUS_AUTHORIZED_NEWKEY: case BOLT_STATUS_AUTHORIZED_SECURE: case BOLT_STATUS_AUTHORIZED_DPONLY: if (pcie) { status_color = bolt_color (ANSI_GREEN); status_text = "authorized"; } else { status_color = bolt_color (ANSI_BLUE); status_text = "connected (no PCIe tunnels)"; } break; case BOLT_STATUS_AUTH_ERROR: status_color = bolt_color (ANSI_RED); status_text = "authorization error"; break; default: status_color = bolt_color (ANSI_NORMAL); status_text = "unknown"; break; } label = bolt_device_get_display_name (dev); g_print (" %s%s%s %s\n", status_color, status_symbol, bolt_color (ANSI_NORMAL), label ? : name); type_text = bolt_device_type_to_string (type); g_print (" %s type: %s\n", tree_branch, type_text); g_print (" %s name: %s\n", tree_branch, name); g_print (" %s vendor: %s\n", tree_branch, vendor); g_print (" %s uuid: %s\n", tree_branch, format_uid (uid)); if (verbose) g_print (" %s dbus path: %s\n", tree_branch, path); if (format_generation (gen, buf, sizeof (buf)) || verbose) g_print (" %s generation: %s\n", tree_branch, buf); g_print (" %s status: %s\n", tree_branch, status_text); if (bolt_status_is_connected (status)) { g_autofree char *flags = NULL; const char *domain; BoltLinkSpeed speed = {0, }; domain = bolt_device_get_domain (dev); g_print (" %s %s domain: %s\n", bolt_glyph (TREE_VERTICAL), tree_branch, domain); if (verbose) { g_print (" %s %s parent: %s\n", bolt_glyph (TREE_VERTICAL), tree_branch, parent); g_print (" %s %s syspath: %s\n", bolt_glyph (TREE_VERTICAL), tree_branch, syspath); } bolt_device_get_linkspeed (dev, &speed); if (speed.rx.lanes && speed.rx.speed) { g_print (" %s %s rx speed: %u Gb/s = %u lanes * %u Gb/s\n", bolt_glyph (TREE_VERTICAL), tree_branch, speed.rx.lanes * speed.rx.speed, speed.rx.lanes, speed.rx.speed); } if (speed.tx.lanes && speed.tx.speed) { g_print (" %s %s tx speed: %u Gb/s = %u lanes * %u Gb/s\n", bolt_glyph (TREE_VERTICAL), tree_branch, speed.tx.lanes * speed.tx.speed, speed.tx.lanes, speed.tx.speed); } flags = bolt_flags_to_string (BOLT_TYPE_AUTH_FLAGS, aflags, NULL); g_print (" %s %s authflags: %s\n", bolt_glyph (TREE_VERTICAL), tree_right, flags); } if (format_timestamp (dev, buf, sizeof (buf), "authtime")) g_print (" %s authorized: %s\n", tree_branch, buf); if (format_timestamp (dev, buf, sizeof (buf), "conntime")) g_print (" %s connected: %s\n", tree_branch, buf); format_timestamp (dev, buf, sizeof (buf), "storetime"); g_print (" %s stored: %s\n", tree_right, buf); if (stored) { const char *pstr = bolt_policy_to_string (policy); const char *kstr; if (keystate == BOLT_KEY_MISSING) kstr = "no"; else if (keystate == BOLT_KEY_HAVE) kstr = "yes"; else if (keystate == BOLT_KEY_NEW) kstr = "yes (new)"; else kstr = "unknown"; g_print (" %s %s policy: %s\n", tree_space, tree_branch, pstr); g_print (" %s %s key: %s\n", tree_space, tree_right, kstr); } g_print ("\n"); } /* **** */ static SubCommand subcommands[] = { {"authorize", authorize, "Authorize a device"}, {"config", config, "Get or set global, device or domain properties"}, {"domains", list_domains, "List the active thunderbolt domains"}, {"enroll", enroll, "Authorize and store a device in the database"}, {"forget", forget, "Remove a stored device from the database"}, {"info", info, "Show information about a device"}, {"list", list_devices, "List connected and stored devices"}, {"monitor", monitor, "Listen and print changes"}, {"power", power, "Force power configuration of the controller"}, {NULL, NULL, NULL}, }; char * subcommands_make_summary (const SubCommand *cmds) { GString *s = g_string_new ("Commands:"); unsigned int spacing = 0; for (const SubCommand *c = cmds; c && c->name; c++) spacing = MAX (spacing, strlen (c->name)); spacing = MAX (15, spacing) + 2; for (const SubCommand *c = cmds; c && c->name; c++) { int space = spacing - strlen (c->name); g_string_append_printf (s, "\n %s", c->name); g_string_append_printf (s, "%*s%s", space, "", c->desc); } return g_string_free (s, FALSE); } const SubCommand * subcommands_find (const SubCommand *cmds, const char *cmdname, GError **error) { const SubCommand *cmd = NULL; for (const SubCommand *c = cmds; !cmd && c && c->name; c++) if (g_str_equal (cmdname, c->name)) cmd = c; if (cmd == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Invalid command: %s", cmdname); return NULL; } return cmd; } int subcommand_run (const SubCommand *cmd, BoltClient *client, int argc, char **argv) { g_autofree char *cmdline = NULL; g_autofree char **args = NULL; int count; cmdline = g_strconcat (g_get_prgname (), " ", cmd->name, NULL); g_set_prgname (cmdline); count = MAX (argc - 1, 1); args = g_new0 (char *, count + 1); args[0] = cmdline; for (int i = 1; i < count; i++) args[i] = argv[i + 1]; return cmd->fn (client, count, args); } int main (int argc, char **argv) { g_autoptr(GOptionContext) optctx = NULL; g_autoptr(BoltClient) client = NULL; g_autoptr(GError) error = NULL; g_autofree char *summary = NULL; const SubCommand *cmd = NULL; const char *cmdname = NULL; const char *uuid_fmtstr = "full"; gboolean version = FALSE; int fmt = -1; GOptionEntry options[] = { { "version", 0, 0, G_OPTION_ARG_NONE, &version, "Print version information and exit", NULL }, { "uuids", 'U', 0, G_OPTION_ARG_STRING, &uuid_fmtstr, "How to format uuids [*full, short, alias]", NULL }, { NULL } }; setlocale (LC_ALL, ""); optctx = g_option_context_new ("[COMMAND]"); g_option_context_add_main_entries (optctx, options, NULL); summary = subcommands_make_summary (subcommands); g_option_context_set_summary (optctx, summary); g_option_context_set_strict_posix (optctx, TRUE); if (!g_option_context_parse (optctx, &argc, &argv, &error)) return usage_error (error); if (version) { g_print ("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); exit (EXIT_SUCCESS); } if (argc < 2) cmdname = "list"; else cmdname = argv[1]; fmt = format_uid_init (uuid_fmtstr, &error); if (fmt == -1) return usage_error (error); client = bolt_client_new (&error); if (!client) return report_error ("could not create client", error); cmd = subcommands_find (subcommands, cmdname, &error); if (cmd == NULL) return usage_error (error); return subcommand_run (cmd, client, argc, argv); } bolt-0.9.2/cli/boltctl.h000066400000000000000000000040021417453051000150410ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-client.h" #include "bolt-term.h" G_BEGIN_DECLS typedef int (*run_t)(BoltClient *client, int argc, char **argv); typedef struct SubCommand { const char *name; run_t fn; const char *desc; } SubCommand; char * subcommands_make_summary (const SubCommand *cmds); const SubCommand * subcommands_find (const SubCommand *cmds, const char *cmdname, GError **error); int subcommand_run (const SubCommand *cmd, BoltClient *client, int argc, char **argv); gboolean check_argc (int argc, int lower, int upper, GError **error); int usage_error (GError *error); int usage_error_need_arg (const char *arg); int usage_error_too_many_args (void); int report_error (const char *prefix, GError *error); void print_device (BoltDevice *dev, gboolean verbose); G_END_DECLS bolt-0.9.2/common/000077500000000000000000000000001417453051000137525ustar00rootroot00000000000000bolt-0.9.2/common/bolt-dbus.c000066400000000000000000000077541417453051000160260ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-dbus.h" #include "bolt-error.h" #include "bolt-dbus-resource.h" #include "bolt-str.h" static gpointer register_dbus_resources (gpointer data) { g_resources_register (bolt_dbus_get_resource ()); return NULL; } void bolt_dbus_ensure_resources (void) { static GOnce only_once = G_ONCE_INIT; g_once (&only_once, register_dbus_resources, NULL); } GDBusInterfaceInfo * bolt_dbus_interface_info_find (const char *interface_xml, const char *interface_name, GError **error) { g_autoptr(GDBusNodeInfo) node = NULL; GDBusInterfaceInfo **iter; GDBusInterfaceInfo *info = NULL; g_return_val_if_fail (interface_xml != NULL, NULL); g_return_val_if_fail (interface_name != NULL, NULL); node = g_dbus_node_info_new_for_xml (interface_xml, error); if (node == NULL) return NULL; for (iter = node->interfaces; iter && *iter; iter++) { GDBusInterfaceInfo *ii = *iter; if (bolt_streq (ii->name, interface_name)) { info = ii; break; } } if (info == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "could not find interface with name '%s", interface_name); return NULL; } return g_dbus_interface_info_ref (info); } GDBusInterfaceInfo * bolt_dbus_interface_info_lookup (const char *resource_name, const char *interface_name, GError **error) { g_autoptr(GBytes) data = NULL; GDBusInterfaceInfo *info; const char *xml; g_return_val_if_fail (resource_name != NULL, NULL); g_return_val_if_fail (interface_name != NULL, NULL); data = g_resources_lookup_data (resource_name, G_RESOURCE_LOOKUP_FLAGS_NONE, error); if (data == NULL) return NULL; xml = g_bytes_get_data (data, NULL); info = bolt_dbus_interface_info_find (xml, interface_name, error); return info; } gboolean bolt_dbus_get_sender_pid (GDBusMethodInvocation *invocation, guint *pid, GError **error) { g_autoptr(GVariant) res = NULL; g_autoptr(GError) err = NULL; GDBusConnection *con; const char *sender; con = g_dbus_method_invocation_get_connection (invocation); sender = g_dbus_method_invocation_get_sender (invocation); res = g_dbus_connection_call_sync (con, "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetConnectionUnixProcessID", g_variant_new ("(s)", sender), G_VARIANT_TYPE ("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err); if (res == NULL) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "could not get pid of caller: %s", err->message); return FALSE; } g_variant_get (res, "(u)", pid); return TRUE; } bolt-0.9.2/common/bolt-dbus.h000066400000000000000000000031311417453051000160140ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS void bolt_dbus_ensure_resources (void); GDBusInterfaceInfo * bolt_dbus_interface_info_find (const char *interface_xml, const char *interface_name, GError **error); GDBusInterfaceInfo * bolt_dbus_interface_info_lookup (const char *resource_name, const char *interface_name, GError **error); gboolean bolt_dbus_get_sender_pid (GDBusMethodInvocation *invocation, guint *pid, GError **error); G_END_DECLS bolt-0.9.2/common/bolt-enums.c000066400000000000000000000260321417453051000162060ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-enums.h" #include "bolt-error.h" #include #if !GLIB_CHECK_VERSION (2, 57, 0) G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref); G_DEFINE_AUTOPTR_CLEANUP_FUNC (GFlagsClass, g_type_class_unref); #endif gboolean bolt_enum_class_validate (GEnumClass *enum_class, gint value, GError **error) { const char *name; gboolean oob; g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); oob = value < enum_class->minimum || value > enum_class->maximum; if (oob) { name = g_type_name_from_class ((GTypeClass *) enum_class); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "enum value '%d' is out of bounds for '%s'", value, name); return FALSE; } return TRUE; } gboolean bolt_enum_validate (GType enum_type, gint value, GError **error) { g_autoptr(GEnumClass) klass = NULL; g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); klass = g_type_class_ref (enum_type); return bolt_enum_class_validate (klass, value, error); } const char * bolt_enum_class_to_string (GEnumClass *klass, gint value, GError **error) { GEnumValue *ev; g_return_val_if_fail (G_IS_ENUM_CLASS (klass), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (!bolt_enum_class_validate (klass, value, error)) return NULL; ev = g_enum_get_value (klass, value); return ev->value_nick; } gboolean bolt_enum_class_from_string (GEnumClass *klass, const char *string, gint *enum_out, GError **error) { const char *name; GEnumValue *ev; g_return_val_if_fail (G_IS_ENUM_CLASS (klass), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (string == NULL) { name = g_type_name_from_class ((GTypeClass *) klass); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "empty string passed for enum class for '%s'", name); return FALSE; } ev = g_enum_get_value_by_nick (klass, string); if (ev == NULL) { name = g_type_name_from_class ((GTypeClass *) klass); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "invalid string '%s' for enum '%s'", string, name); return FALSE; } if (enum_out) *enum_out = ev->value; return TRUE; } const char * bolt_enum_to_string (GType enum_type, gint value, GError **error) { g_autoptr(GEnumClass) klass = NULL; g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); klass = g_type_class_ref (enum_type); return bolt_enum_class_to_string (klass, value, error); } gint bolt_enum_from_string (GType enum_type, const char *string, GError **error) { g_autoptr(GEnumClass) klass = NULL; gint iv = -1; g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); klass = g_type_class_ref (enum_type); bolt_enum_class_from_string (klass, string, &iv, error); return iv; } char * bolt_flags_class_to_string (GFlagsClass *flags_class, guint value, GError **error) { g_autoptr(GString) str = NULL; const char *name; GFlagsValue *fv; g_return_val_if_fail (G_IS_FLAGS_CLASS (flags_class), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); fv = g_flags_get_first_value (flags_class, value); if (fv == NULL) { if (value == 0) return g_strdup (""); name = g_type_name_from_class ((GTypeClass *) flags_class); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "invalid value '%u' for flags '%s'", value, name); return NULL; } value &= ~fv->value; str = g_string_new (fv->value_nick); while (value != 0 && (fv = g_flags_get_first_value (flags_class, value)) != NULL) { g_string_append (str, " | "); g_string_append (str, fv->value_nick); value &= ~fv->value; } if (value != 0) { name = g_type_name_from_class ((GTypeClass *) flags_class); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "unhandled value '%u' for flags '%s'", value, name); return NULL; } return g_string_free (g_steal_pointer (&str), FALSE); } gboolean bolt_flags_class_from_string (GFlagsClass *flags_class, const char *string, guint *flags_out, GError **error) { g_auto(GStrv) vals = NULL; const char *name; guint flags = 0; g_return_val_if_fail (G_IS_FLAGS_CLASS (flags_class), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (string == NULL) { name = g_type_name_from_class ((GTypeClass *) flags_class); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "empty string passed for flags class for '%s'", name); return FALSE; } vals = g_strsplit (string, "|", -1); for (guint i = 0; vals[i]; i++) { GFlagsValue *fv; char *nick; nick = g_strstrip (vals[i]); fv = g_flags_get_value_by_nick (flags_class, nick); if (fv == NULL) { name = g_type_name_from_class ((GTypeClass *) flags_class); g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "invalid flag '%s' for flags '%s'", string, name); return FALSE; } flags |= fv->value; } if (flags_out != NULL) *flags_out = flags; return TRUE; } char * bolt_flags_to_string (GType flags_type, guint value, GError **error) { g_autoptr(GFlagsClass) klass = NULL; g_return_val_if_fail (G_TYPE_IS_FLAGS (flags_type), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); klass = g_type_class_ref (flags_type); return bolt_flags_class_to_string (klass, value, error); } gboolean bolt_flags_from_string (GType flags_type, const char *string, guint *flags_out, GError **error) { g_autoptr(GFlagsClass) klass = NULL; g_return_val_if_fail (G_TYPE_IS_FLAGS (flags_type), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); klass = g_type_class_ref (flags_type); return bolt_flags_class_from_string (klass, string, flags_out, error); } gboolean bolt_flags_update (guint from, guint *to, guint mask) { guint val; gboolean chg; g_return_val_if_fail (to != NULL, FALSE); val = *to & ~mask; /* clear all bits in mask */ val = val | (from & mask); /* set all bits in from and mask */ chg = *to != val; *to = val; return chg; } const char * bolt_status_to_string (BoltStatus status) { return bolt_enum_to_string (BOLT_TYPE_STATUS, status, NULL); } gboolean bolt_status_is_authorized (BoltStatus status) { return status == BOLT_STATUS_AUTHORIZED || status == BOLT_STATUS_AUTHORIZED_SECURE || status == BOLT_STATUS_AUTHORIZED_NEWKEY; } gboolean bolt_status_is_pending (BoltStatus status) { return status == BOLT_STATUS_AUTH_ERROR || status == BOLT_STATUS_CONNECTED; } gboolean bolt_status_is_connected (BoltStatus status) { return status > BOLT_STATUS_DISCONNECTED; } BoltSecurity bolt_security_from_string (const char *str) { return bolt_enum_from_string (BOLT_TYPE_SECURITY, str, NULL); } const char * bolt_security_to_string (BoltSecurity security) { return bolt_enum_to_string (BOLT_TYPE_SECURITY, security, NULL); } gboolean bolt_security_allows_pcie (BoltSecurity security) { gboolean pcie = FALSE; switch (security) { case BOLT_SECURITY_NONE: case BOLT_SECURITY_USER: case BOLT_SECURITY_SECURE: pcie = TRUE; break; case BOLT_SECURITY_DPONLY: case BOLT_SECURITY_USBONLY: case BOLT_SECURITY_UNKNOWN: pcie = FALSE; break; } return pcie; } gboolean bolt_security_is_interactive (BoltSecurity security) { gboolean interactive = FALSE; switch (security) { case BOLT_SECURITY_USER: case BOLT_SECURITY_SECURE: interactive = TRUE; break; case BOLT_SECURITY_NONE: case BOLT_SECURITY_DPONLY: case BOLT_SECURITY_USBONLY: case BOLT_SECURITY_UNKNOWN: interactive = FALSE; break; } return interactive; } const char * bolt_security_for_display (BoltSecurity security, gboolean iommu) { switch (security) { case BOLT_SECURITY_NONE: return iommu ? "iommu" : "none"; case BOLT_SECURITY_USER: return iommu ? "iommu+user" : "user"; case BOLT_SECURITY_SECURE: return iommu ? "iommu+secure" : "secure"; break; default: return bolt_security_to_string (security); } } BoltPolicy bolt_policy_from_string (const char *str) { return bolt_enum_from_string (BOLT_TYPE_POLICY, str, NULL); } const char * bolt_policy_to_string (BoltPolicy policy) { return bolt_enum_to_string (BOLT_TYPE_POLICY, policy, NULL); } gboolean bolt_policy_validate (BoltPolicy policy) { return bolt_enum_validate (BOLT_TYPE_POLICY, policy, NULL); } BoltDeviceType bolt_device_type_from_string (const char *str) { return bolt_enum_from_string (BOLT_TYPE_DEVICE_TYPE, str, NULL); } const char * bolt_device_type_to_string (BoltDeviceType type) { return bolt_enum_to_string (BOLT_TYPE_DEVICE_TYPE, type, NULL); } gboolean bolt_device_type_is_host (BoltDeviceType type) { return type == BOLT_DEVICE_HOST; } char * bolt_auth_mode_to_string (BoltAuthMode mode) { return bolt_flags_to_string (BOLT_TYPE_AUTH_MODE, mode, NULL); } const char * bolt_power_state_to_string (BoltPowerState state) { return bolt_enum_to_string (BOLT_TYPE_POWER_STATE, state, NULL); } bolt-0.9.2/common/bolt-enums.h000066400000000000000000000224051417453051000162130ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-names.h" #include "bolt-enum-types.h" gboolean bolt_enum_validate (GType enum_type, gint value, GError **error); gboolean bolt_enum_class_validate (GEnumClass *enum_class, gint value, GError **error); const char * bolt_enum_class_to_string (GEnumClass *enum_class, gint value, GError **error); gboolean bolt_enum_class_from_string (GEnumClass *enum_class, const char *string, gint *enum_out, GError **error); const char * bolt_enum_to_string (GType enum_type, gint value, GError **error); gint bolt_enum_from_string (GType enum_type, const char *string, GError **error); char * bolt_flags_class_to_string (GFlagsClass *flags_class, guint value, GError **error); gboolean bolt_flags_class_from_string (GFlagsClass *flags_class, const char *string, guint *flags_out, GError **error); char * bolt_flags_to_string (GType flags_type, guint value, GError **error); gboolean bolt_flags_from_string (GType flags_type, const char *string, guint *flags_out, GError **error); gboolean bolt_flags_update (guint from, guint *to, guint mask); #define bolt_flag_isset(flags_, flag_) (!!(flags_ & flag_)) #define bolt_flag_isclear(flags_, flag_) (!(flags_ & flag_)) /** * BoltStatus: * @BOLT_STATUS_UNKNOWN: Device is in an unknown state (should normally not happen). * @BOLT_STATUS_DISCONNECTED: Device is not connected. * @BOLT_STATUS_CONNECTING: Device is currently being connected. * @BOLT_STATUS_CONNECTED: Device is connected, but not authorized. * @BOLT_STATUS_AUTHORIZING: Device is currently authorizing. * @BOLT_STATUS_AUTH_ERROR: Failed to authorize a device via a key. * @BOLT_STATUS_AUTHORIZED: Device connected and authorized. * @BOLT_STATUS_AUTHORIZED_SECURE: Device connected and securely authorized via a key (deprecated). * @BOLT_STATUS_AUTHORIZED_NEWKEY: Device connected and authorized via a new key (deprecated). * @BOLT_STATUS_AUTHORIZED_DPONLY: Device authorized but with thunderbolt disabled (deprecated). * * The current status of the device. */ typedef enum { BOLT_STATUS_UNKNOWN = -1, BOLT_STATUS_DISCONNECTED = 0, BOLT_STATUS_CONNECTING, BOLT_STATUS_CONNECTED, BOLT_STATUS_AUTHORIZING, BOLT_STATUS_AUTH_ERROR, BOLT_STATUS_AUTHORIZED, /* deprecated, do not use */ BOLT_STATUS_AUTHORIZED_SECURE, BOLT_STATUS_AUTHORIZED_NEWKEY, BOLT_STATUS_AUTHORIZED_DPONLY } BoltStatus; const char * bolt_status_to_string (BoltStatus status); gboolean bolt_status_is_authorized (BoltStatus status); gboolean bolt_status_is_connected (BoltStatus status); gboolean bolt_status_is_pending (BoltStatus status); /** * BoltAuthFlags: * @BOLT_AUTH_NONE: No specific authorization. * @BOLT_AUTH_NOPCIE: PCIe tunnels are *not* authorized. * @BOLT_AUTH_SECURE: Device is securely authorized. * @BOLT_AUTH_NOKEY: Device does *not* support key verification. * @BOLT_AUTH_BOOT: Device was already authorized during pre-boot. * * More specific information about device authorization. */ typedef enum { /*< flags >*/ BOLT_AUTH_NONE = 0, BOLT_AUTH_NOPCIE = 1 << 0, BOLT_AUTH_SECURE = 1 << 1, BOLT_AUTH_NOKEY = 1 << 2, BOLT_AUTH_BOOT = 1 << 3, } BoltAuthFlags; /** * BoltKeyState: * @BOLT_KEY_UNKNOWN: unknown key state * @BOLT_KEY_MISSING: no key * @BOLT_KEY_HAVE: key exists * @BOLT_KEY_NEW: key is new * * The state of the key. */ typedef enum { BOLT_KEY_UNKNOWN = -1, BOLT_KEY_MISSING = 0, BOLT_KEY_HAVE = 1, BOLT_KEY_NEW = 2 } BoltKeyState; /** * BoltSecurity: * @BOLT_SECURITY_UNKNOWN : Unknown security. * @BOLT_SECURITY_NONE : No security, all devices are automatically connected. * @BOLT_SECURITY_DPONLY : Display Port only devices only. * @BOLT_SECURITY_USER : User needs to authorize devices. * @BOLT_SECURITY_SECURE : User needs to authorize devices. Authorization can * be done via key exchange to verify the device identity. * @BOLT_SECURITY_USBONLY : Only create a PCIe tunnel to the USB controller in a * connected thunderbolt dock, allowing no downstream PCIe tunnels. * * The security level of the thunderbolt domain. */ typedef enum { BOLT_SECURITY_UNKNOWN = -1, BOLT_SECURITY_NONE = 0, BOLT_SECURITY_DPONLY = 1, BOLT_SECURITY_USER = '1', BOLT_SECURITY_SECURE = '2', BOLT_SECURITY_USBONLY = 4, } BoltSecurity; BoltSecurity bolt_security_from_string (const char *str); const char * bolt_security_to_string (BoltSecurity security); gboolean bolt_security_allows_pcie (BoltSecurity security); gboolean bolt_security_is_interactive (BoltSecurity security); const char * bolt_security_for_display (BoltSecurity security, gboolean iommu); /** * BoltPolicy: * @BOLT_POLICY_UNKNOWN: Unknown policy. * @BOLT_POLICY_DEFAULT: Default policy. * @BOLT_POLICY_MANUAL: Manual authorization of the device. * @BOLT_POLICY_AUTO: Connect the device automatically, * with the best possible security level supported * by the domain controller. * @BOLT_POLICY_IOMMU: Like @BOLT_POLICY_AUTO if IOMMU, i.e. * hardware DMA protection, is active and like @BOLT_POLICY_MANUAL * otherwise: automatically authorize the device if and only if * we have IOMMU active support. * * What do to for connected devices. */ typedef enum { BOLT_POLICY_UNKNOWN = -1, BOLT_POLICY_DEFAULT = 0, BOLT_POLICY_MANUAL = 1, BOLT_POLICY_AUTO = 2, BOLT_POLICY_IOMMU = 3, } BoltPolicy; BoltPolicy bolt_policy_from_string (const char *str); const char * bolt_policy_to_string (BoltPolicy policy); gboolean bolt_policy_validate (BoltPolicy policy); /** * BoltAuthCtrl: * @BOLT_AUTHCTRL_NONE: No authorization flags. * * Control authorization. */ typedef enum { /*< flags >*/ BOLT_AUTHCTRL_NONE = 0 } BoltAuthCtrl; /** * BoltDeviceType: * @BOLT_DEVICE_UNKNOWN_TYPE: Unknown device type * @BOLT_DEVICE_HOST: The device representing the host * @BOLT_DEVICE_PERIPHERAL: A generic thunderbolt peripheral * * The type of the device. */ typedef enum { BOLT_DEVICE_UNKNOWN_TYPE = -1, BOLT_DEVICE_HOST = 0, BOLT_DEVICE_PERIPHERAL } BoltDeviceType; BoltDeviceType bolt_device_type_from_string (const char *str); const char * bolt_device_type_to_string (BoltDeviceType type); gboolean bolt_device_type_is_host (BoltDeviceType type); /** * BoltAuthMode: * @BOLT_AUTH_DISABLED: Authorization is disabled * @BOLT_AUTH_ENABLED: Authorization is enabled. * * Control authorization. */ typedef enum { /*< flags >*/ BOLT_AUTH_DISABLED = 0, BOLT_AUTH_ENABLED = 1 } BoltAuthMode; #define bolt_auth_mode_is_enabled(auth) ((auth & BOLT_AUTH_ENABLED) != 0) #define bolt_auth_mode_is_disabled(auth) (!bolt_auth_mode_is_enabled (auth)) char * bolt_auth_mode_to_string (BoltAuthMode mode); /** * BoltPowerState: * @BOLT_FORCE_POWER_UNSET: Force power was not set by bolt. * @BOLT_FORCE_POWER_OFF: Force power is set to off. * @BOLT_FORCE_POWER_ON: Force power is set to on. * @BOLT_FORCE_POWER_WAIT: Force power is not requested anymore * but still active; so that code can process udev events * properly. * * The force power state that bolt set on thunderbolt controller. */ typedef enum { BOLT_FORCE_POWER_UNSET = -1, BOLT_FORCE_POWER_OFF = 0, BOLT_FORCE_POWER_ON = 1, BOLT_FORCE_POWER_WAIT = 2, } BoltPowerState; const char * bolt_power_state_to_string (BoltPowerState state); bolt-0.9.2/common/bolt-error.c000066400000000000000000000076461417453051000162220ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-error.h" #include "bolt-names.h" #include /** * SECTION:bolt-error * @Title: Error codes * */ static const GDBusErrorEntry bolt_error_entries[] = { {BOLT_ERROR_FAILED, BOLT_DBUS_NAME ".Error.Failed"}, {BOLT_ERROR_UDEV, BOLT_DBUS_NAME ".Error.UDev"}, {BOLT_ERROR_NOKEY, BOLT_DBUS_NAME ".Error.NoKey"}, {BOLT_ERROR_BADKEY, BOLT_DBUS_NAME ".Error.BadKey"}, {BOLT_ERROR_CFG, BOLT_DBUS_NAME ".Error.Cfg"}, {BOLT_ERROR_BADSTATE, BOLT_DBUS_NAME ".Error.BadState"}, {BOLT_ERROR_AUTHCHAIN, BOLT_DBUS_NAME ".Error.AuthChain"}, }; GQuark bolt_error_quark (void) { static volatile gsize quark_volatile = 0; g_dbus_error_register_error_domain ("bolt-error-quark", &quark_volatile, bolt_error_entries, G_N_ELEMENTS (bolt_error_entries)); return (GQuark) quark_volatile; } gboolean bolt_err_notfound (const GError *error) { return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) || g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND) || g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND); } gboolean bolt_err_exists (const GError *error) { return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS) || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_EXIST); } gboolean bolt_err_inval (const GError *error) { return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); } gboolean bolt_err_cancelled (const GError *error) { return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); } gboolean bolt_err_badstate (const GError *error) { return g_error_matches (error, BOLT_ERROR, BOLT_ERROR_BADSTATE); } gboolean bolt_err_nokey (const GError *error) { return g_error_matches (error, BOLT_ERROR, BOLT_ERROR_NOKEY); } gboolean bolt_error_propagate (GError **dest, GError **source) { GError *src; g_return_val_if_fail (source != NULL, FALSE); src = *source; if (src == NULL) return TRUE; g_propagate_error (dest, src); *source = NULL; return FALSE; } gboolean bolt_error_propagate_stripped (GError **dest, GError **source) { GError *src; g_return_val_if_fail (source != NULL, FALSE); src = *source; if (src == NULL) return TRUE; if (g_dbus_error_is_remote_error (src)) g_dbus_error_strip_remote_error (src); g_propagate_error (dest, g_steal_pointer (source)); return FALSE; } gboolean bolt_error_for_errno (GError **error, gint err_no, const char *format, ...) { va_list ap; int code; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (format != NULL, FALSE); if (err_no == 0) return TRUE; if (error == NULL) return FALSE; code = g_io_error_from_errno (err_no); va_start (ap, format); *error = g_error_new_valist (G_IO_ERROR, code, format, ap); va_end (ap); return FALSE; } bolt-0.9.2/common/bolt-error.h000066400000000000000000000043111417453051000162110ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS /** * BoltError: * @BOLT_ERROR_FAILED: Generic error code * @BOLT_ERROR_UDEV: UDev error * @BOLT_ERROR_NOKEY: Key for authorization is missing * @BOLT_ERROR_BADKEY: The key is invalid * @BOLT_ERROR_CFG: Configuration is invalid * @BOLT_ERROR_BADSTATE: Device is in the wrong state * @BOLT_ERROR_AUTHCHAIN: Interrupted authorization chain * * Error codes used inside Bolt. */ typedef enum { BOLT_ERROR_FAILED = 0, BOLT_ERROR_UDEV, BOLT_ERROR_NOKEY, BOLT_ERROR_BADKEY, BOLT_ERROR_CFG, BOLT_ERROR_BADSTATE, BOLT_ERROR_AUTHCHAIN, } BoltError; GQuark bolt_error_quark (void); #define BOLT_ERROR (bolt_error_quark ()) /* helper function to check for certain error types */ gboolean bolt_err_notfound (const GError *error); gboolean bolt_err_exists (const GError *error); gboolean bolt_err_inval (const GError *error); gboolean bolt_err_cancelled (const GError *error); gboolean bolt_err_badstate (const GError *error); gboolean bolt_err_nokey (const GError *error); gboolean bolt_error_propagate (GError **dest, GError **source); gboolean bolt_error_propagate_stripped (GError **dest, GError **source); gboolean bolt_error_for_errno (GError **error, gint err_no, const char *format, ...) G_GNUC_PRINTF (3, 4); G_END_DECLS bolt-0.9.2/common/bolt-fs.c000066400000000000000000000071631417453051000154730ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-error.h" #include "bolt-io.h" #include "bolt-fs.h" #include #include gboolean bolt_fs_make_parent_dirs (GFile *target, GError **error) { g_autoptr(GError) err = NULL; g_autoptr(GFile) p = NULL; gboolean ok; g_return_val_if_fail (G_IS_FILE (target), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); p = g_file_get_parent (target); ok = g_file_make_directory_with_parents (p, NULL, &err); if (!ok && !bolt_err_exists (err)) return bolt_error_propagate (error, &err); return TRUE; } static void cleanup_dir (DIR *d) { struct dirent *de = NULL; while ((de = readdir (d)) != NULL) { g_autoptr(GError) error = NULL; int uflag = 0; if (!g_strcmp0 (de->d_name, ".") || !g_strcmp0 (de->d_name, "..")) continue; if (de->d_type == DT_DIR) { DIR *cd; cd = bolt_opendir_at (dirfd (d), de->d_name, O_RDONLY, &error); if (cd == NULL) continue; cleanup_dir (cd); uflag = AT_REMOVEDIR; closedir (cd); } bolt_unlink_at (dirfd (d), de->d_name, uflag, NULL); } } gboolean bolt_fs_cleanup_dir (const char *target, GError **error) { DIR *d = NULL; g_return_val_if_fail (target != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); d = bolt_opendir (target, error); if (d == NULL) return FALSE; cleanup_dir (d); (void) bolt_closedir (d, NULL); return bolt_rmdir (target, error); } #define TOUCH_FLAGS O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC static inline void timespec_set_from_uint64 (struct timespec *to, guint64 from) { if (from > 0) { to->tv_sec = (time_t) from; to->tv_nsec = 0; } else { to->tv_sec = 0; to->tv_nsec = UTIME_OMIT; } } gboolean bolt_fs_touch (GFile *target, guint64 atime, guint64 mtime, GError **error) { g_autofree char *path = NULL; struct timespec times[2]; gboolean ok; int fd; int r; g_return_val_if_fail (target != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); path = g_file_get_path (target); fd = bolt_open (path, TOUCH_FLAGS, 0664, error); if (fd < 0) return FALSE; /* times[0] is the "last access time" (atime) */ timespec_set_from_uint64 (×[0], atime); /* times[1] is the "last modification time" (mtime). */ timespec_set_from_uint64 (×[1], mtime); r = futimens (fd, times); if (r == -1) { int errsave = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsave), "could not touch file: %s", g_strerror (errsave)); } ok = bolt_close (fd, error); return r != -1 && ok; } bolt-0.9.2/common/bolt-fs.h000066400000000000000000000023661417453051000155000ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "bolt-error.h" #include G_BEGIN_DECLS gboolean bolt_fs_make_parent_dirs (GFile *target, GError **error); gboolean bolt_fs_cleanup_dir (const char *target, GError **error); gboolean bolt_fs_touch (GFile *target, guint64 atime, guint64 mtime, GError **error); G_END_DECLS bolt-0.9.2/common/bolt-glue.c000066400000000000000000000371241417453051000160170ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-glue.h" #include "bolt-enums.h" #include "bolt-str.h" #include GParamSpec * bolt_param_spec_override (GObjectClass *klass, const char *name) { GObjectClass *parent_class = NULL; GParamSpec *base = NULL; GType parent; GType type; guint n; g_return_val_if_fail (G_IS_OBJECT_CLASS (klass), NULL); g_return_val_if_fail (name != NULL, NULL); type = G_OBJECT_CLASS_TYPE (klass); parent = g_type_parent (type); parent_class = g_type_class_peek (parent); if (parent_class) base = g_object_class_find_property (parent_class, name); if (base == NULL) { g_autofree GType *ifaces = g_type_interfaces (type, &n); while (n-- && base == NULL) { gpointer iface = g_type_default_interface_peek (ifaces[n]); if (iface) base = g_object_interface_find_property (iface, name); } } if (base == NULL) { g_critical ("Could not override unknown property: '%s::%s'", G_OBJECT_CLASS_NAME (klass), name); return NULL; } return g_param_spec_override (name, base); } gboolean bolt_str_parse_by_pspec (GParamSpec *spec, const char *str, GValue *val, GError **error) { gboolean ok; if (val->g_type == 0) g_value_init (val, spec->value_type); g_return_val_if_fail (val->g_type == spec->value_type, FALSE); if (G_IS_PARAM_SPEC_BOOLEAN (spec)) { gboolean v; ok = bolt_str_parse_as_boolean (str, &v, error); if (!ok) return FALSE; g_value_set_boolean (val, (gboolean) v); } else if (G_IS_PARAM_SPEC_UINT (spec)) { GParamSpecUInt *s = G_PARAM_SPEC_UINT (spec); guint64 v; ok = bolt_str_parse_as_uint64 (str, &v, error); if (!ok) return FALSE; if (v < s->minimum || v > s->maximum) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "'%" G_GUINT64_FORMAT "' out of range for property", v); return FALSE; } g_value_set_uint (val, (guint) v); } else if (G_IS_PARAM_SPEC_UINT64 (spec)) { GParamSpecUInt64 *s = G_PARAM_SPEC_UINT64 (spec); guint64 v; ok = bolt_str_parse_as_uint64 (str, &v, error); if (!ok) return FALSE; if (v < s->minimum || v > s->maximum) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "'%" G_GUINT64_FORMAT "' out of range for property", v); return FALSE; } g_value_set_uint64 (val, (guint64) v); } else if (G_IS_PARAM_SPEC_ENUM (spec)) { GParamSpecEnum *s = G_PARAM_SPEC_ENUM (spec); gint v; ok = bolt_enum_class_from_string (s->enum_class, str, &v, error); if (!ok) return FALSE; g_value_set_enum (val, v); } else if (G_IS_PARAM_SPEC_FLAGS (spec)) { GParamSpecFlags *s = G_PARAM_SPEC_FLAGS (spec); guint v; ok = bolt_flags_class_from_string (s->flags_class, str, &v, error); if (!ok) return FALSE; g_value_set_flags (val, v); } else if (G_IS_PARAM_SPEC_STRING (spec)) { g_value_set_string (val, str); } else if (G_IS_PARAM_SPEC_BOXED (spec) && spec->value_type == G_TYPE_STRV) { g_auto(GStrv) strv = NULL; strv = g_strsplit (str, ",", -1); g_value_take_boxed (val, g_steal_pointer (&strv)); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "parsing of '%s' is not supported", g_type_name (spec->value_type)); return FALSE; } return TRUE; } GPtrArray * bolt_properties_for_type (GType target) { g_autofree GParamSpec **specs = NULL; GObjectClass *klass; GPtrArray *props; guint n; klass = g_type_class_ref (target); specs = g_object_class_list_properties (klass, &n); props = g_ptr_array_new_full (n, (GDestroyNotify) g_param_spec_unref); for (guint i = 0; i < n; i++) { GParamSpec *s = specs[i]; if (s->owner_type != target) continue; g_ptr_array_add (props, g_param_spec_ref (s)); } return props; } gboolean bolt_properties_find (GPtrArray *specs, const char *name, GParamSpec **spec, GError **error) { if (name == NULL || specs == NULL) return FALSE; for (guint i = 0; i < specs->len; i++) { GParamSpec *s = g_ptr_array_index (specs, i); if (g_str_equal (name, s->name) || bolt_streq (name, g_param_spec_get_nick (s))) { *spec = s; return TRUE; } } g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "property '%s' not found", name); return FALSE; } typedef enum { BOLT_WIRE_CONV_NATIVE, BOLT_WIRE_CONV_CUSTOM, BOLT_WIRE_CONV_ENUM_AS_STRING, BOLT_WIRE_CONV_FLAGS_AS_STRING, BOLT_WIRE_CONV_OBJECT_AS_STRING, } BoltWireConvType; struct _BoltWireConv { /* book-keeping */ volatile gint ref_count; /* */ GVariantType *wire_type; GParamSpec *prop_spec; /* */ BoltWireConvType conv_type; BoltConvToWire to_wire; BoltConvFromWire from_wire; /* */ char *custom_id; }; /* helper */ static inline gboolean value_is_initialized (GValue *value) { return G_VALUE_TYPE (value) != 0; } static void wire_conv_init_value_if_needed (BoltWireConv *conv, GValue *value) { if (!value_is_initialized (value)) g_value_init (value, conv->prop_spec->value_type); } /* internal conversions */ static GVariant * conv_enum_to_str (BoltWireConv *conv, const GValue *value, GError **error) { GParamSpecEnum *es = G_PARAM_SPEC_ENUM (conv->prop_spec); const char *str = NULL; gint iv; iv = g_value_get_enum (value); str = bolt_enum_class_to_string (es->enum_class, iv, error); if (str == NULL) return NULL; return g_variant_new_string (str); } static gboolean conv_enum_from_str (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error) { GParamSpecEnum *es; const char *str; gboolean ok; gint v; es = G_PARAM_SPEC_ENUM (conv->prop_spec); str = g_variant_get_string (wire, NULL); /* NB: it is ok to pass NULL for 'str' */ ok = bolt_enum_class_from_string (es->enum_class, str, &v, error); if (ok) g_value_set_enum (value, v); else g_value_set_enum (value, es->default_value); return ok; } static GVariant * conv_flags_to_str (BoltWireConv *conv, const GValue *value, GError **error) { GParamSpecFlags *fs; char *str; guint uv; fs = G_PARAM_SPEC_FLAGS (conv->prop_spec); uv = g_value_get_flags (value); str = bolt_flags_class_to_string (fs->flags_class, uv, error); if (str == NULL) return NULL; return g_variant_new_take_string (str); } static gboolean conv_flags_from_str (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error) { GParamSpecFlags *fs; gboolean ok; const char *str; guint v; fs = G_PARAM_SPEC_FLAGS (conv->prop_spec); str = g_variant_get_string (wire, NULL); /* NB: NULL is a safe value for 'str' to be passed into */ ok = bolt_flags_class_from_string (fs->flags_class, str, &v, error); if (ok) g_value_set_flags (value, v); else g_value_set_flags (value, fs->default_value); return ok; } static GVariant * conv_obj_to_str (BoltWireConv *conv, const GValue *value, GError **error) { GVariant *res = NULL; char *str = NULL; GObject *obj; obj = g_value_get_object (value); if (obj) g_object_get (obj, "object-id", &str, NULL); else str = g_strdup (""); if (str) res = g_variant_new_take_string (str); if (!res) g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "failed to convert object to string"); return res; } static gboolean conv_obj_from_str (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "failed to convert object from string"); return FALSE; } static GVariant * conv_value_to_variant (BoltWireConv *conv, const GValue *value, GError **error) { return g_dbus_gvalue_to_gvariant (value, conv->wire_type); } static gboolean conv_value_from_variant (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error) { GType want = value->g_type; g_dbus_gvariant_to_gvalue (wire, value); /* if the value was initialized before, make sure * we got the same type */ if (want != 0 && value->g_type != want) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Can not convert wire from %s to %s", g_type_name (value->g_type), g_type_name (want)); return FALSE; } return TRUE; } static GVariant * conv_str_to_wire (BoltWireConv *conv, const GValue *value, GError **error) { const char *str = g_value_get_string (value); if (str == NULL) str = ""; return g_variant_new_string (str); } static gboolean conv_str_from_wire (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error) { const char *str = g_variant_get_string (wire, NULL); if (str && *str == '\0') str = NULL; g_value_set_static_string (value, str); return TRUE; } /* public interfaces */ BoltWireConv * bolt_wire_conv_ref (BoltWireConv *conv) { g_return_val_if_fail (conv != NULL, NULL); g_return_val_if_fail (conv->ref_count > 0, NULL); g_atomic_int_inc (&conv->ref_count); return conv; } void bolt_wire_conv_unref (BoltWireConv *conv) { g_return_if_fail (conv != NULL); g_return_if_fail (conv->ref_count > 0); if (g_atomic_int_dec_and_test (&conv->ref_count)) { g_variant_type_free (conv->wire_type); g_param_spec_unref (conv->prop_spec); g_clear_pointer (&conv->custom_id, g_free); g_free (conv); } } const GVariantType * bolt_wire_conv_get_wire_type (BoltWireConv *conv) { g_return_val_if_fail (conv != NULL, NULL); g_return_val_if_fail (conv->ref_count > 0, NULL); return conv->wire_type; } const GParamSpec * bolt_wire_conv_get_prop_spec (BoltWireConv *conv) { g_return_val_if_fail (conv != NULL, NULL); g_return_val_if_fail (conv->ref_count > 0, NULL); return conv->prop_spec; } gboolean bolt_wire_conv_is_native (BoltWireConv *conv) { g_return_val_if_fail (conv != NULL, TRUE); return conv->conv_type == BOLT_WIRE_CONV_NATIVE; } const char * bolt_wire_conv_describe (BoltWireConv *conv) { g_return_val_if_fail (conv != NULL, "*invalid*"); switch (conv->conv_type) { case BOLT_WIRE_CONV_NATIVE: return "native"; case BOLT_WIRE_CONV_ENUM_AS_STRING: return "enum-as-string"; case BOLT_WIRE_CONV_FLAGS_AS_STRING: return "flags-as-string"; case BOLT_WIRE_CONV_OBJECT_AS_STRING: return "object-as-string"; case BOLT_WIRE_CONV_CUSTOM: if (conv->custom_id) return conv->custom_id; return "custom"; } return "*unknown*"; } BoltWireConv * bolt_wire_conv_for (const GVariantType *wire_type, GParamSpec *prop_spec) { BoltWireConv *conv; gboolean as_str; g_return_val_if_fail (wire_type != NULL, NULL); g_return_val_if_fail (prop_spec != NULL, NULL); conv = g_new (BoltWireConv, 1); conv->ref_count = 1; conv->wire_type = g_variant_type_copy (wire_type); conv->prop_spec = g_param_spec_ref (prop_spec); conv->custom_id = NULL; as_str = g_variant_type_equal (wire_type, G_VARIANT_TYPE_STRING); if (as_str && G_IS_PARAM_SPEC_ENUM (prop_spec)) { conv->conv_type = BOLT_WIRE_CONV_ENUM_AS_STRING; conv->to_wire = conv_enum_to_str; conv->from_wire = conv_enum_from_str; } else if (as_str && G_IS_PARAM_SPEC_FLAGS (prop_spec)) { conv->conv_type = BOLT_WIRE_CONV_FLAGS_AS_STRING; conv->to_wire = conv_flags_to_str; conv->from_wire = conv_flags_from_str; } else if (as_str && G_IS_PARAM_SPEC_OBJECT (prop_spec)) { conv->conv_type = BOLT_WIRE_CONV_OBJECT_AS_STRING; conv->to_wire = conv_obj_to_str; conv->from_wire = conv_obj_from_str; } else if (as_str && G_IS_PARAM_SPEC_STRING (prop_spec)) { conv->conv_type = BOLT_WIRE_CONV_NATIVE; conv->to_wire = conv_str_to_wire; conv->from_wire = conv_str_from_wire; } else { conv->conv_type = BOLT_WIRE_CONV_NATIVE; conv->to_wire = conv_value_to_variant; conv->from_wire = conv_value_from_variant; } return conv; } BoltWireConv * bolt_wire_conv_custom (const GVariantType *wire_type, GParamSpec *prop_spec, const char *custom_id, BoltConvToWire to_wire, BoltConvFromWire from_wire) { BoltWireConv *conv; g_return_val_if_fail (wire_type != NULL, NULL); g_return_val_if_fail (prop_spec != NULL, NULL); g_return_val_if_fail (wire_type != NULL, NULL); g_return_val_if_fail (prop_spec != NULL, NULL); conv = g_new (BoltWireConv, 1); conv->ref_count = 1; conv->conv_type = BOLT_WIRE_CONV_CUSTOM; conv->wire_type = g_variant_type_copy (wire_type); conv->prop_spec = g_param_spec_ref (prop_spec); conv->custom_id = g_strdup (custom_id); conv->to_wire = to_wire; conv->from_wire = from_wire; return conv; } GVariant * bolt_wire_conv_to_wire (BoltWireConv *conv, const GValue *value, GError **error) { GVariant *res = NULL; g_return_val_if_fail (conv != NULL, NULL); g_return_val_if_fail (G_IS_VALUE (value), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); res = conv->to_wire (conv, value, error); if (res == NULL) return NULL; if (g_variant_is_floating (res)) g_variant_ref_sink (res); return res; } gboolean bolt_wire_conv_from_wire (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error) { gboolean ok; g_return_val_if_fail (conv != NULL, FALSE); g_return_val_if_fail (wire != NULL, FALSE); g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); wire_conv_init_value_if_needed (conv, value); ok = conv->from_wire (conv, wire, value, error); return ok; } bolt-0.9.2/common/bolt-glue.h000066400000000000000000000071451417453051000160240ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS GParamSpec * bolt_param_spec_override (GObjectClass *object_class, const char *name); gboolean bolt_str_parse_by_pspec (GParamSpec *spec, const char *str, GValue *val, GError **error); GPtrArray * bolt_properties_for_type (GType target); gboolean bolt_properties_find (GPtrArray *specs, const char *name, GParamSpec **spec, GError **error); /* wire protocol variant/value conversions */ typedef struct _BoltWireConv BoltWireConv; typedef GVariant * (*BoltConvToWire) (BoltWireConv *conv, const GValue *value, GError **error); typedef gboolean (*BoltConvFromWire) (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error); BoltWireConv * bolt_wire_conv_ref (BoltWireConv *conv); void bolt_wire_conv_unref (BoltWireConv *conv); const GVariantType * bolt_wire_conv_get_wire_type (BoltWireConv *conv); const GParamSpec * bolt_wire_conv_get_prop_spec (BoltWireConv *conv); gboolean bolt_wire_conv_is_native (BoltWireConv *conv); const char * bolt_wire_conv_describe (BoltWireConv *conv); BoltWireConv * bolt_wire_conv_for (const GVariantType *wire_type, GParamSpec *prop_spec); BoltWireConv * bolt_wire_conv_custom (const GVariantType *wire_type, GParamSpec *prop_spec, const char *custom_id, BoltConvToWire to_wire, BoltConvFromWire from_wire); GVariant * bolt_wire_conv_to_wire (BoltWireConv *conv, const GValue *value, GError **error); gboolean bolt_wire_conv_from_wire (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error); G_DEFINE_AUTOPTR_CLEANUP_FUNC (BoltWireConv, bolt_wire_conv_unref) #if !GLIB_CHECK_VERSION (2, 57, 1) G_DEFINE_AUTOPTR_CLEANUP_FUNC (GParamSpec, g_param_spec_unref) #endif G_END_DECLS bolt-0.9.2/common/bolt-io.c000066400000000000000000000536161417453051000154760ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include #include #include #include #include #include #include #include #if !HAVE_FN_COPY_FILE_RANGE #include #include # ifndef __NR_copy_file_range # if defined(__x86_64__) # define __NR_copy_file_range 326 # elif defined(__i386__) # define __NR_copy_file_range 377 # else # error "__NR_copy_file_range on this architecture" # endif # endif #endif #include "bolt-error.h" #include "bolt-str.h" #include "bolt-io.h" G_DEFINE_AUTOPTR_CLEANUP_FUNC (FILE, fclose); /* standard open flags for overwriting a file, i.e. close on * exec (3), open in write only mode, truncate before writing, * and create it if it does not yet exist */ #define BOLT_O_OVERWRITE (O_CLOEXEC | O_WRONLY | O_TRUNC | O_CREAT) int bolt_open (const char *path, int flags, int mode, GError **error) { int fd; g_return_val_if_fail (error == NULL || *error == NULL, -1); fd = g_open (path, flags, mode); if (fd < 0) { gint code = g_io_error_from_errno (errno); g_set_error (error, G_IO_ERROR, code, "could not open '%s': %s", path, g_strerror (errno)); return -1; } return fd; } static FILE * bolt_fdopen (int fd, const char *mode, GError **error) { FILE *fp; g_return_val_if_fail (error == NULL || *error == NULL, NULL); fp = fdopen (fd, mode); if (fp == NULL) { gint code = g_io_error_from_errno (errno); g_set_error (error, G_IO_ERROR, code, "fdopen ('%d') error: %s", fd, g_strerror (errno)); return NULL; } return fp; } gboolean bolt_close (int fd, GError **error) { int r; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = close (fd); if (r == 0) return TRUE; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not close file: %s", g_strerror (errno)); return FALSE; } gboolean bolt_read_all (int fd, void *buf, gsize nbytes, gsize *nread, GError **error) { char *data = buf; gsize count = 0; gboolean ok = TRUE; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); do { ssize_t n; n = read (fd, data, nbytes); if (n < 0) { if (errno == EINTR) continue; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "read error: %s", g_strerror (errno)); ok = FALSE; break; } else if (n == 0) { break; } data += n; count += n; nbytes -= n; } while (nbytes > 0); if (nread) *nread = count; return ok; } gboolean bolt_write_all (int fd, const void *buf, gssize nbytes, GError **error) { const char *data = buf; gboolean ok = TRUE; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (nbytes < 0) nbytes = strlen (data); do { ssize_t n; n = write (fd, data, nbytes); if (n < 0) { if (errno == EINTR) continue; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "write error: %s", g_strerror (errno)); ok = FALSE; } else if (nbytes > 0 && n == 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (EIO), "write error (zero write)"); ok = FALSE; } if (!ok) break; data += n; nbytes -= n; } while (nbytes > 0); return ok; } DIR * bolt_opendir (const char *path, GError **error) { DIR *d = NULL; g_return_val_if_fail (error == NULL || *error == NULL, NULL); d = opendir (path); if (d == NULL) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not open directory ('%s'): %s", path, g_strerror (errno)); return NULL; } return d; } int bolt_openat (int dirfd, const char *path, int oflag, int mode, GError **error) { int fd = -1; g_return_val_if_fail (path != NULL, -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); fd = openat (dirfd, path, oflag, mode); if (fd < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Could not open file %s: %s", path, g_strerror (errno)); } return fd; } DIR * bolt_opendir_at (int dirfd, const char *name, int oflag, GError **error) { int fd = -1; DIR *cd; g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); fd = bolt_openat (dirfd, name, oflag, 0, error); if (fd < 0) return NULL; cd = fdopendir (fd); if (cd == NULL) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed to open directory: %s", g_strerror (errno)); (void) close (fd); } return cd; } gboolean bolt_closedir (DIR *d, GError **error) { int r; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = closedir (d); if (r < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed close dir: %s", g_strerror (errno)); return FALSE; } return TRUE; } gboolean bolt_mkdirat (int dirfd, const char *name, mode_t mode, GError **error) { int r = 0; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = mkdirat (dirfd, name, mode); if (r < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed to create directory '%s': %s", name, g_strerror (errno)); return FALSE; } return TRUE; } gboolean bolt_rmdir (const char *name, GError **error) { int r; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = rmdir (name); if (r < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed to remove directory '%s': %s", name, g_strerror (errno)); return FALSE; } return TRUE; } gboolean bolt_unlink (const char *name, GError **error) { int r; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = unlink (name); if (r < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed to unlink '%s': %s", name, g_strerror (errno)); return FALSE; } return TRUE; } gboolean bolt_unlink_at (int dirfd, const char *name, int flag, GError **error) { int r; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = unlinkat (dirfd, name, flag); if (r < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed to unlink '%s': %s", name, g_strerror (errno)); return FALSE; } return TRUE; } gboolean bolt_write_file_at (int dirfd, const char *name, const char *data, gssize len, GError **error) { int fd; gboolean ok; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); fd = bolt_openat (dirfd, name, BOLT_O_OVERWRITE, 0666, error); if (fd < 0) return FALSE; ok = bolt_write_all (fd, data, len, error); if (!ok) (void) close (fd); else ok = bolt_close (fd, error); return ok; } char * bolt_read_value_at (int dirfd, const char *name, GError **error) { g_autoptr(FILE) fp = NULL; char line[LINE_MAX], *l; int fd; g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); fd = bolt_openat (dirfd, name, O_NOFOLLOW | O_CLOEXEC | O_RDONLY, 0, error); if (fd < 0) return NULL; fp = bolt_fdopen (fd, "re", error); if (!fp) { g_prefix_error (error, "could not open %s: ", name); return NULL; } l = fgets (line, sizeof (line) - 1, fp); if (!l) { if (ferror (fp)) { if (errno < 1) errno = -EIO; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "io error of file %s: %s", name, g_strerror (errno)); return NULL; } line[0] = '\0'; } g_strstrip (line); return g_strdup (line); } gboolean bolt_write_char_at (int dirfd, const char *name, char value, GError **error) { int fd; ssize_t n; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); fd = bolt_openat (dirfd, name, O_WRONLY | O_CLOEXEC, 0, error); if (fd < 0) return FALSE; retry: n = write (fd, &value, 1); if (n == -1) { int errsv = errno; if (errsv == EINTR) goto retry; if (errsv == ENOKEY) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_NOKEY, "device does not contain a key"); } else if (errsv == EKEYREJECTED) { g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_BADKEY, "key was rejected"); } else { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), "write error: %s", g_strerror (errno)); } } (void) close (fd); return n > 0; } gboolean bolt_write_int_at (int dirfd, const char *name, gint val, GError **error) { char buf[256] = {0, }; gsize n; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); n = g_snprintf (buf, sizeof (buf), "%d", val); g_assert (n < sizeof (buf)); return bolt_write_file_at (dirfd, name, buf, n, error); } gboolean bolt_read_int_at (int dirfd, const char *name, gint *val, GError **error) { g_autofree char *str = NULL; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); str = bolt_read_value_at (dirfd, name, error); if (str == NULL) return FALSE; return bolt_str_parse_as_int (str, val, error); } gboolean bolt_write_uint_at (int dirfd, const char *name, guint val, GError **error) { char buf[256] = {0, }; gsize n; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); n = g_snprintf (buf, sizeof (buf), "%u", val); g_assert (n < sizeof (buf)); return bolt_write_file_at (dirfd, name, buf, n, error); } gboolean bolt_read_uint_at (int dirfd, const char *name, guint *val, GError **error) { g_autofree char *str = NULL; g_return_val_if_fail (name != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); str = bolt_read_value_at (dirfd, name, error); if (str == NULL) return FALSE; return bolt_str_parse_as_uint (str, val, error); } gboolean bolt_verify_uid (int dirfd, const char *want, GError **error) { g_autoptr(GError) err = NULL; g_autofree char *have = NULL; gsize want_len; gsize have_len; gboolean ok; g_return_val_if_fail (want != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); have = bolt_read_value_at (dirfd, "unique_id", &err); if (have == NULL) { /* make clang's static analyzer happy */ g_return_val_if_fail (err != NULL, FALSE); g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "unique id verification failed: %s", err->message); return FALSE; } have_len = strlen (have); want_len = strlen (want); ok = have_len == want_len && !memcmp (want, have, have_len); if (!ok) g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "unique id verification failed [%s != %s]", have, want); return ok; } gboolean bolt_file_write_all (const char *fn, const void *data, gssize n, GError **error) { g_return_val_if_fail (fn != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); return bolt_write_file_at (AT_FDCWD, fn, data, n, error); } gboolean bolt_ftruncate (int fd, off_t size, GError **error) { int r; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = ftruncate (fd, size); if (r == -1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not truncate file: %s", g_strerror (errno)); return FALSE; } return TRUE; } int bolt_mkfifo (const char *path, mode_t mode, GError **error) { int r; g_return_val_if_fail (path != NULL, -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); r = mkfifo (path, mode); if (r == -1) { gint code = g_io_error_from_errno (errno); g_set_error (error, G_IO_ERROR, code, "could not create FIFO at '%s': %s", path, g_strerror (errno)); return -1; } return r; } gboolean bolt_faddflags (int fd, int flags, GError **error) { int cur; int code; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); cur = fcntl (fd, F_GETFL); if (cur != -1) { cur |= flags; cur = fcntl (fd, F_SETFL, cur); } if (cur != -1) return TRUE; code = g_io_error_from_errno (errno); g_set_error (error, G_IO_ERROR, code, "could not add flags to fd: %s", g_strerror (errno)); return FALSE; } gboolean bolt_fstat (int fd, struct stat *statbuf, GError **error) { int code; int r; g_return_val_if_fail (statbuf != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = fstat (fd, statbuf); if (r == 0) return TRUE; code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "could not stat file: %s", g_strerror (code)); return FALSE; } gboolean bolt_fstatat (int dirfd, const char *pathname, struct stat *statbuf, int flags, GError **error) { int code; int r; g_return_val_if_fail (dirfd > -1, FALSE); g_return_val_if_fail (pathname != NULL, FALSE); g_return_val_if_fail (statbuf != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = fstatat (dirfd, pathname, statbuf, flags); if (r == 0) return TRUE; code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "could not stat file '%s': %s", pathname, g_strerror (code)); return FALSE; } gboolean bolt_fdatasync (int fd, GError **error) { int code; int r; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = fdatasync (fd); if (r == 0) return TRUE; code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "could not sync file data : %s", g_strerror (code)); return FALSE; } gboolean bolt_lseek (int fd, off_t offset, int whence, int *pos, GError **error) { off_t p; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); p = lseek (fd, offset, whence); if (p == (off_t) -1) { int code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "could not seek file: %s", g_strerror (code)); return FALSE; } if (pos) *pos = p; return TRUE; } gboolean bolt_rename (const char *from, const char *to, GError **error) { int code; int r; g_return_val_if_fail (from != NULL, FALSE); g_return_val_if_fail (to != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = rename (from, to); if (r == 0) return TRUE; code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "could not rename '%s' to '%s': %s", from, to, g_strerror (code)); return FALSE; } gboolean bolt_renameat (int from_dir, const char *from, int to_dir, const char *to, GError **error) { int code; int r; g_return_val_if_fail (from != NULL, FALSE); g_return_val_if_fail (to != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); r = renameat (from_dir, from, to_dir, to); if (r == 0) return TRUE; code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "could not rename '%s' to '%s': %s", from, to, g_strerror (code)); return FALSE; } #if !HAVE_FN_COPY_FILE_RANGE static loff_t copy_file_range (int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags) { return syscall (__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); } #endif gboolean bolt_copy_bytes (int fd_from, int fd_to, size_t len, GError **error) { g_return_val_if_fail (fd_from > -1, FALSE); g_return_val_if_fail (fd_to > -1, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); do { ssize_t r; r = copy_file_range (fd_from, NULL, fd_to, NULL, len, 0); if (r == -1) { int code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "error while copying data: %s", g_strerror (code)); return FALSE; } else if (r == 0) { break; } len -= r; } while (len > 0); return len == 0; } gboolean bolt_dir_is_empty (DIR *dir, gboolean *empty, GError **error) { struct dirent *de = NULL; long n; g_return_val_if_fail (dir != NULL, FALSE); g_return_val_if_fail (empty != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); *empty = TRUE; n = telldir (dir); if (n == -1) { int code = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (code), "telldir(3) failed: %s", g_strerror (code)); return FALSE; } rewinddir (dir); while ((de = readdir (dir)) != NULL) { if (!g_strcmp0 (de->d_name, ".") || !g_strcmp0 (de->d_name, "..")) continue; *empty = FALSE; break; } seekdir (dir, n); return TRUE; } /* auto cleanup helpers */ void bolt_cleanup_close_intpr (int *fd) { g_return_if_fail (fd != NULL); if (*fd > -1) { int errsave = errno; int r; r = close (*fd); if (r != 0 && errno == EBADF) g_warning ("invalid fd passed to auto cleanup"); errno = errsave; } } bolt-0.9.2/common/bolt-io.h000066400000000000000000000143311417453051000154720ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include #include #include #include #include #include "bolt-macros.h" G_BEGIN_DECLS /* *INDENT-OFF* */ G_DEFINE_AUTOPTR_CLEANUP_FUNC (DIR, closedir); /* *INDENT-ON* */ int bolt_open (const char *path, int flags, int mode, GError **error); gboolean bolt_close (int fd, GError **error); gboolean bolt_read_all (int fd, void *buf, gsize nbytes, gsize *nread, GError **error); gboolean bolt_write_all (int fd, const void *buf, gssize nbytes, GError **error); gboolean bolt_ftruncate (int fd, off_t size, GError **error); DIR * bolt_opendir (const char *path, GError **error); DIR * bolt_opendir_at (int dirfd, const char *name, int oflag, GError **error); gboolean bolt_closedir (DIR *d, GError **error); gboolean bolt_mkdirat (int dirfd, const char *name, mode_t mode, GError **error); gboolean bolt_rmdir (const char *name, GError **error); int bolt_openat (int dirfd, const char *path, int oflag, int mode, GError **error); gboolean bolt_unlink (const char *name, GError **error); gboolean bolt_unlink_at (int dirfd, const char *name, int flag, GError **error); gboolean bolt_write_file_at (int dirfd, const char *name, const char *data, gssize len, GError **error); char * bolt_read_value_at (int dirfd, const char *name, GError **error); gboolean bolt_write_char_at (int dirfd, const char *name, char value, GError **error); gboolean bolt_write_int_at (int dirfd, const char *name, gint val, GError **error); gboolean bolt_read_int_at (int dirfd, const char *name, gint *val, GError **error); gboolean bolt_write_uint_at (int dirfd, const char *name, guint val, GError **error); gboolean bolt_read_uint_at (int dirfd, const char *name, guint *val, GError **error); gboolean bolt_verify_uid (int dirfd, const char *uid, GError **error); gboolean bolt_file_write_all (const char *fn, const void *data, gssize n, GError **error); int bolt_mkfifo (const char *path, mode_t mode, GError **error); gboolean bolt_faddflags (int fd, int flags, GError **error); gboolean bolt_fstat (int fd, struct stat *statbuf, GError **error); gboolean bolt_fstatat (int dirfd, const char *pathname, struct stat *statbuf, int flags, GError **error); gboolean bolt_fdatasync (int fd, GError **error); gboolean bolt_lseek (int fd, off_t offset, int whence, int *pos, GError **error); gboolean bolt_rename (const char *from, const char *to, GError **error); gboolean bolt_renameat (int from_dir, const char *from, int to_dir, const char *to, GError **error); gboolean bolt_copy_bytes (int fd_from, int fd_to, size_t len, GError **error); gboolean bolt_dir_is_empty (DIR *dir, gboolean *empty, GError **error); /* auto cleanup for I/O handles */ void bolt_cleanup_close_intpr (int *fd); #define bolt_autoclose bolt_cleanup (bolt_cleanup_close_intpr) G_END_DECLS bolt-0.9.2/common/bolt-list.h000066400000000000000000000066511417453051000160440ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include "bolt-macros.h" G_BEGIN_DECLS typedef struct BoltList_ BoltList; struct BoltList_ { BoltList *next; BoltList *prev; }; static inline void bolt_list_init (BoltList *node) { node->next = node; node->prev = node; } static inline void bolt_list_add_internal (BoltList *prev, BoltList *next, BoltList *node) { node->next = next; node->prev = prev; next->prev = node; prev->next = node; } static inline BoltList * bolt_list_add_before (BoltList *pos, BoltList *node) { if (pos == NULL) return node; bolt_list_add_internal (pos->prev, pos, node); return pos; } static inline BoltList * bolt_list_add_after (BoltList *pos, BoltList *node) { if (pos == NULL) return node; bolt_list_add_internal (pos, pos->next, node); return pos; } static inline void bolt_list_del_internal (BoltList *prev, BoltList *next) { prev->next = next; next->prev = prev; } #define bolt_list_entry(node, type, member) bolt_container_of (node, type, member) /* no head list */ static inline guint bolt_nhlist_len (BoltList *list) { BoltList *i = list; guint count = 0; if (list == NULL) return count; do { count++; i = i->next; } while (i != list); return count; } static inline BoltList * bolt_nhlist_del (BoltList *list, BoltList *node) { if (node == NULL || list == NULL) return list; bolt_list_del_internal (node->prev, node->next); if (node == list) { if (list->next == list) return NULL; else return list->next; } return list; } /* * Using a BoltList struct an iterator, where: * next the *current* node * prev the *head* of the list * * The following special conditions are encoded: * sym | next | prev | state * S | * | NULL | first iteration, initial state * E | NULL | * | end of iteration, final state */ static inline BoltList * bolt_nhlist_iter_init (BoltList *iter, BoltList *list) { iter->next = list; iter->prev = NULL; return iter; } #define bolt_nhlist_iter_head(iter_) ((iter_)->prev ? : (iter_)->next) #define bolt_nhlist_iter_node(iter_) ((iter_ != NULL) ? (iter_)->next : NULL) #define bolt_nhlist_iter_entry(iter_, type, member) \ bolt_container_of (bolt_nhlist_iter_node (iter_), type, member) static inline BoltList * bolt_nhlist_iter_next (BoltList *iter) { g_return_val_if_fail (iter != NULL, NULL); if (iter->next == NULL) /* E */ return NULL; if (iter->prev == NULL) /* S */ { iter->prev = iter->next; return iter->next; } iter->next = iter->next->next; if (iter->next == iter->prev) iter->next = NULL; return iter->next; } G_END_DECLS bolt-0.9.2/common/bolt-macros.h000066400000000000000000000034671417453051000163570ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include #include G_BEGIN_DECLS #ifndef offsetof #define offsetof(type, member) ((size_t) &((type *) 0)->member) #endif #define bolt_container_of(ptr, type, member) \ ({const typeof (((type *) 0)->member) * p__ = (ptr); \ (type *) ((void *) ((char *) p__ - offsetof (type, member))); }) /* *INDENT-OFF* */ #define bolt_swap(a, b) G_STMT_START { \ typeof (a) t__ = (a); \ (a) = (b); \ (b) = t__; \ } G_STMT_END #define bolt_steal(ptr, none_value) ({ \ typeof (*(ptr)) t__ = *(ptr); \ *(ptr) = (none_value); \ t__; \ }) /* *INDENT-ON* */ #define bolt_cleanup(x) __attribute__((cleanup (x))) #if defined(__SANITIZE_ADDRESS__) # define HAVE_ASAN 1 #elif defined(__has_feature) # if __has_feature (address_sanitizer) # define HAVE_ASAN 1 # endif #endif #ifndef HAVE_ASAN # define HAVE_ASAN 0 #endif G_END_DECLS bolt-0.9.2/common/bolt-names.c000066400000000000000000000026501417453051000161620ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-names.h" /* Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_" */ #define DBUS_OPATH_VALID_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" char * bolt_gen_object_path (const char *base, const char *oid) { g_autofree char *id = NULL; if (oid) { id = g_strdup (oid); g_strcanon (id, DBUS_OPATH_VALID_CHARS, '_'); } if (base && id) return g_build_path ("/", "/", base, id, NULL); else if (base) return g_build_path ("/", "/", base, NULL); else if (id) return g_build_path ("/", "/", id, NULL); return g_strdup ("/"); } bolt-0.9.2/common/bolt-names.h000066400000000000000000000061461417453051000161730ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS /* D-Bus API revision (here for the lack of a better place) */ #define BOLT_DBUS_API_VERSION 1U /* logging */ #define BOLT_LOG_DOMAIN_UID "BOLT_DOMAIN_UID" #define BOLT_LOG_DOMAIN_NAME "BOLT_DOMAIN_NAME" #define BOLT_LOG_DEVICE_UID "BOLT_DEVICE_UID" #define BOLT_LOG_DEVICE_NAME "BOLT_DEVICE_NAME" #define BOLT_LOG_DEVICE_STATE "BOLT_DEVICE_STATE" #define BOLT_LOG_ERROR_DOMAIN "ERROR_DOMAIN" #define BOLT_LOG_ERROR_CODE "ERROR_CODE" #define BOLT_LOG_ERROR_MESSAGE "ERROR_MESSAGE" #define BOLT_LOG_TOPIC "BOLT_TOPIC" #define BOLT_LOG_VERSION "BOLT_VERSION" #define BOLT_LOG_CONTEXT "BOLT_LOG_CONTEXT" #define BOLT_LOG_BUG_MARK "BOLT_LOG_BUG" /* logging - message ids */ #define BOLT_LOG_MSG_IDLEN 33 #define BOLT_LOG_MSG_ID_STARTUP "dd11929c788e48bdbb6276fb5f26b08a" /* dbus */ #define BOLT_DBUS_GRESOURCE_PATH "/bolt/org.freedesktop.bolt.xml" #define BOLT_DBUS_NAME "org.freedesktop.bolt" #define BOLT_DBUS_PATH "/org/freedesktop/bolt" #define BOLT_DBUS_PATH_DOMAINS BOLT_DBUS_PATH "/domains" #define BOLT_DBUS_PATH_DEVICES BOLT_DBUS_PATH "/devices" #define BOLT_DBUS_INTERFACE "org.freedesktop.bolt1.Manager" #define BOLT_DBUS_DEVICE_INTERFACE "org.freedesktop.bolt1.Device" #define BOLT_DBUS_DOMAIN_INTERFACE "org.freedesktop.bolt1.Domain" #define BOLT_DBUS_POWER_INTERFACE "org.freedesktop.bolt1.Power" /* sysfs */ #define BOLT_SYSFS_UNIQUE_ID "unique_id" #define BOLT_SYSFS_IOMMU "iommu_dma_protection" #define BOLT_SYSFS_GENERATION "generation" #define BOLT_SYSFS_RX_LANES "rx_lanes" #define BOLT_SYSFS_RX_SPEED "rx_speed" #define BOLT_SYSFS_TX_LANES "tx_lanes" #define BOLT_SYSFS_TX_SPEED "tx_speed" #define BOLT_SYSFS_DMI_ID "/sys/class/dmi/id" #define BOLT_SYSFS_DMI_SYS_VENDOR "sys_vendor" #define BOLT_SYSFS_DMI_PRODUCT_NAME "product_name" #define BOLT_SYSFS_DMI_PRODUCT_VERSION "product_version" /* environment variables */ #define BOLT_ENV_DBPATH "BOLT_DBPATH" #define BOLT_ENV_RUNTIME_DIRECTORY "RUNTIME_DIRECTORY" #define BOLT_ENV_STATE_DIRECTORY "STATE_DIRECTORY" #define BOLT_SD_WATCHDOG_USEC "WATCHDOG_USEC" #define BOLT_SD_NOTIFY_SOCKET "NOTIFY_SOCKET" /* other well known names */ #define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341" /* helper functions */ char * bolt_gen_object_path (const char *path_base, const char *object_id); G_END_DECLS bolt-0.9.2/common/bolt-rnd.c000066400000000000000000000056041417453051000156440ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-io.h" #include "bolt-rnd.h" #include #include #include #if HAVE_FN_GETRANDOM #include # else # define GRND_NONBLOCK 0 #endif int bolt_get_random_data (void *buf, gsize n) { gboolean ok; ok = bolt_random_getrandom (buf, n, GRND_NONBLOCK, NULL); if (ok) return BOLT_RNG_GETRANDOM; ok = bolt_random_urandom (buf, n); if (ok) return BOLT_RNG_URANDOM; bolt_random_prng (buf, n); return BOLT_RNG_PRNG; } /* specific implementations */ gboolean bolt_random_getrandom (void *buf, gsize n, unsigned flags, GError **error) { int r = -1; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); #if HAVE_FN_GETRANDOM r = getrandom (buf, n, flags); #else errno = ENOSYS; #endif if (r < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed to get random data: %s", g_strerror (errno)); return FALSE; } return TRUE; } gboolean bolt_random_urandom (void *buf, gsize n) { gboolean ok; int rndfd; gsize len; g_return_val_if_fail (buf != NULL, FALSE); rndfd = bolt_open ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY, 0, NULL); if (rndfd < 0) return FALSE; ok = bolt_read_all (rndfd, buf, n, &len, NULL); (void) close (rndfd); /* NB: according to the man page random(4), "when calling read(2) for the device /dev/urandom, reads of up to 256 bytes will return as many bytes as are requested and will not be interrupted by a signal handler". */ return ok && len == n; } void bolt_random_prng (void *buf, gsize n) { char *ptr = buf; const gsize l = n % sizeof (guint32); const gsize k = n - l; if (buf == NULL || n == 0) return; for (gsize i = 0; i < k; i += sizeof (guint32)) { guint32 r = g_random_int (); memcpy (ptr + i, &r, sizeof (guint32)); } if (l > 0) { guint32 r = g_random_int (); memcpy (ptr + k, &r, l); } } bolt-0.9.2/common/bolt-rnd.h000066400000000000000000000026441417453051000156520ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS /* general function */ typedef enum { BOLT_RNG_ERROR = -1, BOLT_RNG_URANDOM = 1, BOLT_RNG_PRNG = 2, BOLT_RNG_GETRANDOM = 3, } BoltRng; BoltRng bolt_get_random_data (void *buf, gsize n); /* specific implementations */ gboolean bolt_random_getrandom (void *buf, gsize n, unsigned flags, GError **error); gboolean bolt_random_urandom (void *buf, gsize n); void bolt_random_prng (void *buf, gsize n); G_END_DECLS bolt-0.9.2/common/bolt-str.c000066400000000000000000000237451417453051000156770ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-error.h" #include "bolt-macros.h" #include "bolt-str.h" #include #include #include typedef void (* zero_fn_t) (void *s, size_t n); void bolt_erase_n (void *data, gsize n) { #if !HAVE_FN_EXPLICIT_BZERO #warning no explicit bzero, using fallback static volatile zero_fn_t explicit_bzero = bzero; #endif explicit_bzero (data, n); } void bolt_str_erase (char *str) { if (str == NULL) return; bolt_erase_n (str, strlen (str)); } void bolt_str_erase_clear (char **str) { g_return_if_fail (str != NULL); if (*str == NULL) return; bolt_str_erase (*str); g_free (*str); *str = NULL; } GStrv bolt_strv_from_ptr_array (GPtrArray **array) { GPtrArray *a; if (array == NULL || *array == NULL) return NULL; a = *array; if (a->len == 0 || a->pdata[a->len - 1] != NULL) g_ptr_array_add (a, NULL); *array = NULL; return (GStrv) g_ptr_array_free (a, FALSE); } GStrv bolt_strv_make_n (guint size, const char *init) { GPtrArray *a; a = g_ptr_array_sized_new (size + 1); for (guint i = 0; i < size; i++) g_ptr_array_add (a, g_strdup (init)); return bolt_strv_from_ptr_array (&a); } gsize bolt_strv_length (char * const *strv) { gsize l = 0; if (strv == NULL) return 0; while (*strv++ != NULL) l++; return l; } guint bolt_gstrv_length0 (const GStrv strv) { if (strv == NULL) return 0; return g_strv_length ((char **) strv); } char ** bolt_strv_contains (GStrv haystack, const char *needle) { g_return_val_if_fail (needle != NULL, NULL); if (haystack == NULL) return NULL; for (char **iter = haystack; *iter != NULL; iter++) if (bolt_streq (*iter, needle)) return iter; return NULL; } gboolean bolt_strv_equal (const GStrv a, const GStrv b) { guint na, nb; if (a == b) return TRUE; na = a == NULL ? 0 : g_strv_length (a); nb = b == NULL ? 0 : g_strv_length (b); if (na != nb) return FALSE; for (guint i = 0; i < na; i++) if (!bolt_streq (a[i], b[i])) return FALSE; return TRUE; } GHashTable * bolt_strv_diff (const GStrv before, const GStrv after) { GHashTable *diff = NULL; diff = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); /* build an index of everything in 'before', marking is as * deleted for now */ for (char **iter = before; iter && *iter; iter++) { char *key = *iter; if (bolt_strzero (key)) continue; g_hash_table_insert (diff, g_strdup (key), GINT_TO_POINTER ('-')); } for (char **iter = after; iter && *iter; iter++) { char *key = *iter; gboolean have; if (bolt_strzero (*iter)) continue; have = g_hash_table_contains (diff, key); /* if *iter is also in before, we have it in both GStrv and * thus we remove it from the diff; otherwise it is a new * string and we add it to the diff */ if (have) g_hash_table_remove (diff, key); else g_hash_table_insert (diff, g_strdup (key), GINT_TO_POINTER ('+')); } return diff; } char ** bolt_strv_rotate_left (char **strv) { char *start; char **prev; char **iter; if (strv == NULL || *strv == NULL) return NULL; start = *strv; /* remember the first element */ for (prev = strv, iter = strv + 1; *iter; prev = iter, iter++) *prev = *iter; *prev = start; return prev; } void bolt_strv_permute (char **strv) { guint n; if (strv == NULL || *strv == NULL) return; n = bolt_strv_length (strv); g_return_if_fail (n > 0); /* Knuth shuffles */ for (guint i = 0; i < (n - 1) && strv[i]; i++) { /* random int k, with i ≤ k < n, * i.e. random_int_range returns k ∈ [i, n-1] */ gsize k = g_random_int_range (i, n); bolt_swap (strv[i], strv[k]); } } gboolean bolt_uuidv_check (const GStrv uuidv, gboolean empty_ok, GError **error) { if (bolt_strv_isempty (uuidv)) { if (empty_ok) return TRUE; g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "provided uuid array is empty"); return FALSE; } for (char * const *p = uuidv; *p; p++) { const char *uuid = *p; if (bolt_strzero (uuid) && empty_ok) continue; if (!g_uuid_string_is_valid (uuid)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "entry '%s' is not a valid UUID", uuid); return FALSE; } } return TRUE; } char * bolt_strdup_validate (const char *string) { g_autofree char *str = NULL; gboolean ok; gsize l; if (string == NULL) return NULL; str = g_strdup (string); str = g_strstrip (str); l = strlen (str); if (l == 0) return NULL; ok = g_utf8_validate (str, l, NULL); if (!ok) return NULL; return g_steal_pointer (&str); } char * bolt_strstrip (char *string) { char *str; if (string == NULL) return NULL; str = g_strstrip (string); if (strlen (str) == 0) g_clear_pointer (&str, g_free); return str; } gboolean bolt_str_parse_as_int (const char *str, gint *ret, GError **error) { char *end; gint64 val; g_return_val_if_fail (str != NULL, -1); errno = 0; val = g_ascii_strtoll (str, &end, 0); if (val == 0 && (errno != 0 || str == end)) { if (errno == 0) errno = EINVAL; g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "failed to parse '%s' as unsigned integer", str); return FALSE; } else if (((val == G_MAXINT64 || val == G_MININT64) && errno == ERANGE) || val > G_MAXINT || val < G_MININT) { if (errno == 0) errno = ERANGE; g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "parsing '%s' overflows unsigned integer", str); return FALSE; } if (ret) *ret = (gint) val; return TRUE; } gboolean bolt_str_parse_as_uint (const char *str, guint *ret, GError **error) { guint64 val; gboolean ok; ok = bolt_str_parse_as_uint64 (str, &val, error); if (!ok) return FALSE; if (val > G_MAXUINT) { errno = ERANGE; g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "parsing '%s' overflows uint", str); return FALSE; } if (ret) *ret = (guint) val; return TRUE; } gboolean bolt_str_parse_as_uint64 (const char *str, guint64 *ret, GError **error) { guint64 val; char *end; g_return_val_if_fail (str != NULL, FALSE); errno = 0; val = g_ascii_strtoull (str, &end, 0); if (val == 0 && (errno != 0 || str == end)) { if (errno == 0) errno = EINVAL; g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "failed to parse '%s' as unsigned integer", str); return FALSE; } else if (val == G_MAXUINT64 && errno == ERANGE) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "parsing '%s' overflows unsigned integer", str); return FALSE; } if (ret) *ret = val; return TRUE; } gboolean bolt_str_parse_as_uint32 (const char *str, guint32 *ret, GError **error) { gboolean ok; guint64 val; ok = bolt_str_parse_as_uint64 (str, &val, error); if (!ok) return FALSE; if (val > G_MAXUINT32) { errno = ERANGE; g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "parsing '%s' overflows uint32", str); return FALSE; } if (ret) *ret = (guint32) val; return TRUE; } gboolean bolt_str_parse_as_boolean (const char *str, gboolean *ret, GError **error) { struct { const char *s; gboolean v; } table[] = { {"1", TRUE}, {"0", FALSE}, /* true, false */ {"t", TRUE}, {"f", FALSE}, {"true", TRUE}, {"false", FALSE}, /* yes, no */ {"y", TRUE}, {"n", FALSE}, {"yes", TRUE}, {"no", FALSE}, /* on, off */ {"on", TRUE}, {"off", FALSE}, }; g_return_val_if_fail (str != NULL, FALSE); for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { if (bolt_strcaseeq (table[i].s, str)) { if (ret) *ret = table[i].v; return TRUE; } } g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "failed to parse '%s' as boolean", str); return FALSE; } gboolean bolt_set_strdup_printf (char **target, const char *fmt, ...) { va_list args; char *str; g_return_val_if_fail (target != NULL, FALSE); g_return_val_if_fail (fmt != NULL, FALSE); va_start (args, fmt); str = g_strdup_vprintf (fmt, args); va_end (args); return bolt_set_str (target, str); } gint bolt_comparefn_strcmp (gconstpointer a, gconstpointer b) { const char * const *astrv = a; const char * const *bstrv = b; return g_strcmp0 (*astrv, *bstrv); } bolt-0.9.2/common/bolt-str.h000066400000000000000000000070451417453051000156770ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include G_BEGIN_DECLS void bolt_erase_n (void *data, gsize n); void bolt_str_erase (char *str); void bolt_str_erase_clear (char **str); #define bolt_streq(s1, s2) (g_strcmp0 ((s1), (s2)) == 0) #define bolt_strcaseeq(s1, s2) (g_ascii_strcasecmp ((s1), (s2)) == 0) GStrv bolt_strv_from_ptr_array (GPtrArray **array); GStrv bolt_strv_make_n (guint size, const char *init); gsize bolt_strv_length (char * const *strv); guint bolt_gstrv_length0 (const GStrv strv); char ** bolt_strv_contains (GStrv haystack, const char *needle); gboolean bolt_strv_equal (const GStrv a, const GStrv b); GHashTable * bolt_strv_diff (const GStrv before, const GStrv after); char ** bolt_strv_rotate_left (char **strv); void bolt_strv_permute (char **strv); #define bolt_strv_isempty(strv) ((strv) == NULL || *(strv) == NULL) gboolean bolt_uuidv_check (const GStrv uuidv, gboolean empty_ok, GError **error); #define bolt_strzero(str) (str == NULL || *str == '\0') #define bolt_yesno(val) val ? "yes" : "no" #define bolt_okfail(val) (!!val) ? "ok" : "fail" char *bolt_strdup_validate (const char *string); char *bolt_strstrip (char *string); gboolean bolt_str_parse_as_int (const char *str, gint *ret, GError **error); gboolean bolt_str_parse_as_uint (const char *str, guint *ret, GError **error); gboolean bolt_str_parse_as_uint64 (const char *str, guint64 *ret, GError **error); gboolean bolt_str_parse_as_uint32 (const char *str, guint32 *ret, GError **error); gboolean bolt_str_parse_as_boolean (const char *str, gboolean *ret, GError **error); /* replacing string pointers, like g_set_object, g_set_error ... */ static inline gboolean bolt_set_str (char **target, char *str) { char *ptr; g_return_val_if_fail (target != NULL, FALSE); ptr = *target; if (ptr == str) return FALSE; g_free (ptr); *target = str; return TRUE; } #define bolt_set_strdup(target, str) \ bolt_set_str (target, g_strdup (str)) gboolean bolt_set_strdup_printf (char **target, const char *fmt, ...) G_GNUC_PRINTF (2, 3); gint bolt_comparefn_strcmp (gconstpointer a, gconstpointer b); G_END_DECLS bolt-0.9.2/common/bolt-term.c000066400000000000000000000053261417453051000160310ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-str.h" #include "bolt-term.h" #include #include int bolt_is_fancy_terminal (void) { const char *val; if (!isatty (STDOUT_FILENO)) return 0; val = g_getenv ("TERM"); if (val && !g_ascii_strcasecmp (val, "dumb")) return 0; return 1; } const char * bolt_color (const char *color) { static gint on = -1; if (G_UNLIKELY (on == -1)) on = bolt_is_fancy_terminal (); return on ? color : ""; } /* borrowed from systemd */ static const char *const ansi_glphys[BOLT_GLYPH_LAST] = { [TREE_VERTICAL] = "| ", [TREE_BRANCH] = "|-", [TREE_RIGHT] = "`-", [TREE_SPACE] = " ", [WHITE_CIRCLE] = "o", [BLACK_CIRCLE] = "*", [ARROW] = "->", [MDASH] = "-", [ELLIPSIS] = "...", [WARNING_SIGN] = "!", }; static const char *const utf8_glphys[BOLT_GLYPH_LAST] = { [TREE_VERTICAL] = "\342\224\202 ", /* │ */ [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ [TREE_SPACE] = " ", /* */ [WHITE_CIRCLE] = "\342\227\213", /* ○ */ [BLACK_CIRCLE] = "\342\227\217", /* ● */ [ARROW] = "\342\206\222", /* → */ [MDASH] = "\342\200\223", /* – */ [ELLIPSIS] = "\342\200\246", /* … */ [WARNING_SIGN] = "\342\232\240", /* ⚠ */ }; const char * bolt_glyph (BoltGlyph g) { static const char *const *glyph_table = NULL; g_return_val_if_fail (g < BOLT_GLYPH_LAST, "?"); /* TODO: check for utf-8 support */ if (G_UNLIKELY (glyph_table == NULL)) { if (bolt_is_fancy_terminal ()) glyph_table = utf8_glphys; else glyph_table = ansi_glphys; } return glyph_table[g]; } bolt-0.9.2/common/bolt-term.h000066400000000000000000000026331417453051000160340ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include G_BEGIN_DECLS #define ANSI_NORMAL "\x1B[0m" #define ANSI_RED "\x1B[0;31m" #define ANSI_GREEN "\x1B[0;32m" #define ANSI_YELLOW "\x1B[0;33m" #define ANSI_BLUE "\x1B[0;34m" #define ANSI_HIGHLIGHT_BLACK "\x1B[0;1;30m" #define ANSI_HIGHLIGHT_RED "\x1B[0;1;31m" int bolt_is_fancy_terminal (void); const char * bolt_color (const char *color); typedef enum { TREE_VERTICAL, TREE_BRANCH, TREE_RIGHT, TREE_SPACE, BLACK_CIRCLE, WHITE_CIRCLE, ARROW, MDASH, ELLIPSIS, WARNING_SIGN, BOLT_GLYPH_LAST } BoltGlyph; const char * bolt_glyph (BoltGlyph g); G_END_DECLS bolt-0.9.2/common/bolt-time.c000066400000000000000000000023151417453051000160130ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-time.h" char * bolt_epoch_format (guint64 seconds, const char *format) { g_autoptr(GDateTime) dt = NULL; g_return_val_if_fail (format != NULL, NULL); dt = g_date_time_new_from_unix_utc ((gint64) seconds); if (dt == NULL) return NULL; return g_date_time_format (dt, format); } guint64 bolt_now_in_seconds (void) { gint64 now = g_get_real_time (); return (guint64) now / G_USEC_PER_SEC; } bolt-0.9.2/common/bolt-time.h000066400000000000000000000022041417453051000160150ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS #define BOLT_MSEC_PER_SEC 1000LL /* number of milli-seconds (ms) in a second (s) */ #define BOLT_USEC_PER_MSEC 1000LL /* number of micro-seconds (µs) in a ms */ char * bolt_epoch_format (guint64 seconds, const char *format); guint64 bolt_now_in_seconds (void); G_END_DECLS bolt-0.9.2/common/bolt-unix.c000066400000000000000000000100151417453051000160340ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-unix.h" #include "bolt-error.h" #include "bolt-io.h" #include "bolt-names.h" #include "bolt-str.h" #include #include #include #include #include gboolean bolt_pid_is_alive (pid_t pid) { gulong p = (gulong) pid; char path[256]; if (pid != 0) g_snprintf (path, sizeof (path), "/proc/%lu/stat", p); else g_snprintf (path, sizeof (path), "/proc/self/stat"); return g_file_test (path, G_FILE_TEST_EXISTS); } gboolean bolt_sd_notify_literal (const char *state, gboolean *sent, GError **error) { bolt_autoclose int fd = -1; struct sockaddr_un sau = {AF_UNIX, }; struct msghdr msghdr = { NULL, }; struct iovec iovec = { }; const char *env; socklen_t socklen; size_t len; ssize_t r; g_return_val_if_fail (state != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (sent) *sent = FALSE; env = g_getenv (BOLT_SD_NOTIFY_SOCKET); if (env == NULL) return TRUE; len = strlen (env); if (len > sizeof (sau.sun_path) - 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "unix domain socket path too long: %s", env); return FALSE; } socklen = offsetof (struct sockaddr_un, sun_path) + len; switch (*env) { case '@': /* abstract sockets have sun_path[0] set to '\0', * otherwise '\0' have no special meaning, i.e. * the address is not null-terminted */ memcpy (sau.sun_path + 1, env + 1, len); socklen += 1; /* the leading '\0' */ break; case '/': /* pathname: null-terminated filesystem path */ memcpy (sau.sun_path, env, len + 1); break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported socket address: %s", env); return FALSE; } fd = socket (AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) return bolt_error_for_errno (error, errno, "failed to open socket %m"); iovec.iov_base = (void *) state; iovec.iov_len = strlen (state); msghdr.msg_iov = &iovec; msghdr.msg_iovlen = 1; msghdr.msg_name = &sau; msghdr.msg_namelen = socklen; r = sendmsg (fd, &msghdr, MSG_NOSIGNAL); if (sent) *sent = TRUE; if (r < 0) { bolt_error_for_errno (error, errno, "failed to send msg: %m"); return FALSE; } else if ((size_t) r != iovec.iov_len) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE, "failed to send complete message: %s", env); return FALSE; } return TRUE; } int bolt_sd_watchdog_enabled (guint64 *timeout, GError **error) { const char *str; guint64 val = 0; gboolean ok; str = g_getenv (BOLT_SD_WATCHDOG_USEC); if (str == NULL) return 0; ok = bolt_str_parse_as_uint64 (str, &val, error); if (ok && (val == 0 || val == (guint64) - 1)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid value '%" G_GUINT64_FORMAT "'", val); ok = FALSE; } if (!ok) g_prefix_error (error, "failed to parse WATCHDOG_USEC: "); else if (timeout) *timeout = val; return ok ? 1 : -1; } bolt-0.9.2/common/bolt-unix.h000066400000000000000000000022461417453051000160500ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include G_BEGIN_DECLS gboolean bolt_pid_is_alive (pid_t pid); gboolean bolt_sd_notify_literal (const char *state, gboolean *sent, GError **error); int bolt_sd_watchdog_enabled (guint64 *timeout, GError **error); G_END_DECLS bolt-0.9.2/common/bolt-wire.c000066400000000000000000000055661417453051000160360ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-wire.h" #include "bolt-error.h" #include G_DEFINE_BOXED_TYPE (BoltLinkSpeed, bolt_link_speed, bolt_link_speed_copy, g_free); BoltLinkSpeed * bolt_link_speed_copy (const BoltLinkSpeed *other) { BoltLinkSpeed *copy = g_new (BoltLinkSpeed, 1); *copy = *other; return copy; } gboolean bolt_link_speed_equal (const BoltLinkSpeed *a, const BoltLinkSpeed *b) { return a->rx.speed == b->rx.speed && a->rx.lanes == b->rx.lanes && a->tx.speed == b->tx.speed && a->tx.lanes == b->tx.lanes; } GVariant * bolt_link_speed_to_wire (BoltWireConv *conv, const GValue *value, GError **error) { GVariantBuilder builder; BoltLinkSpeed *link; link = g_value_get_boxed (value); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{su}")); g_variant_builder_add (&builder, "{su}", "rx.speed", link->rx.speed); g_variant_builder_add (&builder, "{su}", "rx.lanes", link->rx.lanes); g_variant_builder_add (&builder, "{su}", "tx.speed", link->tx.speed); g_variant_builder_add (&builder, "{su}", "tx.lanes", link->tx.lanes); return g_variant_builder_end (&builder); } gboolean bolt_link_speed_from_wire (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error) { BoltLinkSpeed link; gboolean ok; struct { const char *name; guint32 *target; } entries[] = { {"rx.speed", &link.rx.speed}, {"rx.lanes", &link.rx.lanes}, {"tx.speed", &link.tx.speed}, {"tx.lanes", &link.tx.lanes}, }; for (unsigned i = 0; i < G_N_ELEMENTS (entries); i++) { const char *name = entries[i].name; guint32 *target = entries[i].target; ok = g_variant_lookup (wire, name, "u", target); if (!ok) { g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "Missing entry in LinkSpeed dict: %s", name); return FALSE; } } g_value_set_boxed (value, &link); return ok; } bolt-0.9.2/common/bolt-wire.h000066400000000000000000000034701417453051000160330ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include G_BEGIN_DECLS typedef struct BoltLinkSpeed_ BoltLinkSpeed; struct BoltLinkSpeed_ { struct { struct { guint32 speed; guint32 lanes; } rx; struct { guint32 speed; guint32 lanes; } tx; }; }; GType bolt_link_speed_get_type (void); #define BOLT_TYPE_LINK_SPEED (bolt_link_speed_get_type ()) BoltLinkSpeed * bolt_link_speed_copy (const BoltLinkSpeed *ls); gboolean bolt_link_speed_equal (const BoltLinkSpeed *a, const BoltLinkSpeed *b); GVariant * bolt_link_speed_to_wire (BoltWireConv *conv, const GValue *value, GError **error); gboolean bolt_link_speed_from_wire (BoltWireConv *conv, GVariant *wire, GValue *value, GError **error); G_END_DECLS bolt-0.9.2/common/fix-coverity.h000066400000000000000000000061531417453051000165600ustar00rootroot00000000000000/* * fix-coverity.h * Copyright 2017 Peter Jones * * Distributed under terms of the LGPL 2.1+ license. */ #ifndef FIX_COVERITY_H #define FIX_COVERITY_H #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef __COVERITY_GCC_VERSION_AT_LEAST #define __COVERITY_GCC_VERSION_AT_LEAST(x, y) 0 #define FAKE__COVERITY_GCC_VERSION_AT_LEAST__ #endif /* __COVERITY_GCC_VERSION_AT_LEAST */ /* With gcc 7 on x86_64 (at least), coverity pretends to be GCC but * accidentally doesn't create all of the types GCC would. * * In glibc's headers, bits/floatn.h has: * * #if (defined __x86_64__ \ * ? __GNUC_PREREQ (4, 3) \ * : (defined __GNU__ ? __GNUC_PREREQ (4, 5) : __GNUC_PREREQ (4, 4))) * # define __HAVE_FLOAT128 1 * #else * # define __HAVE_FLOAT128 0 * #endif * * and stdlib.h has: * * #if __HAVE_FLOAT128 && __GLIBC_USE (IEC_60559_TYPES_EXT) * slash* Likewise for the '_Float128' format *slash * extern _Float128 strtof128 (const char *__restrict __nptr, * char **__restrict __endptr) * __THROW __nonnull ((1)); * #endif * * Which then causes cov-emit to lose its shit: * * "/usr/include/stdlib.h", line 133: error #20: identifier "_Float128" is * undefined * extern _Float128 strtof128 (const char *__restrict __nptr, * ^ * "/usr/include/stdlib.h", line 190: error #20: identifier "_Float128" is * undefined * _Float128 __f) * ^ * "/usr/include/stdlib.h", line 236: error #20: identifier "_Float128" is * undefined * extern _Float128 strtof128_l (const char *__restrict __nptr, * ^ * * And then you'll notice something like this later on: * [WARNING] Emitted 0 C/C++ compilation units (0%) successfully * * 0 C/C++ compilation units (0%) are ready for analysis * For more details, please look at: * /home/pjones/devel/github.com/dbxtool/master/cov-int/build-log.txt * * You would think that if you're writing something that pretends to be * gcc, and you've got a "build a configuration by running shit through gcc * and looking at the output" stage (which they do), you would run "gcc -da * -fdump-tree-all -c -o foo.o foo.c" on an empty file and snarf up all the * types defined in the foo.c.001t.tu output. Apparently, they do not. * * Anyway, even just defining the type doesn't always work in the face of * how _Complex is defined, so we cheat a bit here. Be prepared to vomit. */ #ifdef __x86_64__ #if __COVERITY_GCC_VERSION_AT_LEAST (7, 0) #if 0 typedef float _Float128 __attribute__((__mode__ (__TF__))); typedef __complex__ float __cfloat128 __attribute__((__mode__ (__TC__))); typedef _Complex float __cfloat128 __attribute__((__mode__ (__TC__))); #else #include #define __cplusplus 201103L #include #undef __cplusplus #endif #endif #endif #ifdef FAKE__COVERITY_GCC_VERSION_AT_LEAST__ #undef FAKE__COVERITY_GCC_VERSION_AT_LEAST #undef __COVERITY_GCC_VERSION_AT_LEAST #endif #endif /* !FIX_COVERITY_H */ // vim:fenc=utf-8:tw=75 bolt-0.9.2/config.h.in000066400000000000000000000030731417453051000145100ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once /* naming and versioning */ #mesondefine PACKAGE_NAME #mesondefine PACKAGE_VERSION #mesondefine VERSION #mesondefine VERSION_MAJOR #mesondefine VERSION_MINOR /* directories */ #mesondefine DATADIR #mesondefine LOCALSTATEDIR #mesondefine BOLT_DBNAME #mesondefine BOLT_DBDIR #mesondefine DATADIR /* availability of features */ #mesondefine HAVE_FN_EXPLICIT_BZERO #mesondefine HAVE_FN_GETRANDOM #mesondefine HAVE_FN_COPY_FILE_RANGE #mesondefine HAVE_POLKIT_AUTOPTR /* constants */ #mesondefine _GNU_SOURCE /* fixup coverity build issues */ #mesondefine IS_COVERITY_BUILD #ifdef IS_COVERITY_BUILD #include "fix-coverity.h" #endif /* use structure logging f */ #define G_LOG_USE_STRUCTURED 1 /* glib version checking */ #mesondefine GLIB_VERSION_MIN_REQUIRED #mesondefine GLIB_VERSION_MAX_ALLOWED bolt-0.9.2/contrib/000077500000000000000000000000001417453051000141225ustar00rootroot00000000000000bolt-0.9.2/contrib/Dockerfile-alpine000066400000000000000000000003721417453051000173640ustar00rootroot00000000000000## -*- mode: dockerfile -*- FROM alpine:latest ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN apk add --no-cache bash dbus dbus-dev eudev-dev gcc glib-dev libc-dev meson ninja polkit-dev udev RUN mkdir /src /build WORKDIR /src bolt-0.9.2/contrib/Dockerfile-arch000066400000000000000000000007561417453051000170370ustar00rootroot00000000000000## -*- mode: dockerfile -*- FROM archlinux/archlinux:base-devel ENV LANG en_US.UTF-8 ENV LC_ALL en_US.UTF-8 RUN pacman -Syu --noconfirm RUN pacman -S --noconfirm base-devel RUN pacman -S --noconfirm \ asciidoc \ dbus-glib \ git \ gobject-introspection \ gtk-doc \ meson \ perl-sgmls \ polkit \ python-dbus \ python-gobject \ python-pip \ umockdev \ valgrind RUN pip3 install python-dbusmock pylint==2.4.1 RUN mkdir /src /build WORKDIR /src bolt-0.9.2/contrib/Dockerfile-coverity000066400000000000000000000017501417453051000177610ustar00rootroot00000000000000## -*- mode: dockerfile -*- FROM fedora:34 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN dnf --enablerepo=updates-testing -y update RUN dnf --enablerepo=updates-testing -y install \ clang-analyzer \ codespell \ gcc \ git \ glib2-devel \ glibc-langpack-en \ gtk-doc \ lcov \ libgudev-devel \ meson-0.56.2-2.fc34 \ polkit-devel \ python3 \ python3-dbus \ python3-dbusmock \ python3-gobject \ rpm-build \ redhat-rpm-config \ systemd-devel \ umockdev-devel \ uncrustify \ wget ARG TOKEN ARG PROJECT ARG ORG ENV HOME "/root" WORKDIR "$HOME" RUN curl https://scan.coverity.com/download/linux64 \ --form project=${ORG}/${PROJECT} \ --form token=${TOKEN} \ -o coverity_tool.tgz && \ tar zxf coverity_tool.tgz \ && rm coverity_tool.tgz && \ mv cov-analysis-linux64-* cov-analysis-linux64 ENV PATH "$PATH:$HOME/cov-analysis-linux64/bin" RUN mkdir /src /build WORKDIR /src bolt-0.9.2/contrib/Dockerfile-debian000066400000000000000000000007351417453051000173410ustar00rootroot00000000000000## -*- mode: dockerfile -*- FROM debian:sid RUN apt-get update -q && apt-get install -y \ asciidoc \ git \ gobject-introspection \ libgirepository1.0-dev \ libpolkit-gobject-1-dev \ locales \ libudev-dev \ libumockdev-dev \ libsystemd-dev \ meson \ pkg-config \ policykit-1 \ python3-dbus \ python3-dbusmock \ udev \ umockdev \ systemd \ && rm -rf /var/lib/apt/lists/* RUN mkdir /src /build WORKDIR /src bolt-0.9.2/contrib/Dockerfile-fedora000066400000000000000000000011361417453051000173530ustar00rootroot00000000000000## -*- mode: dockerfile -*- FROM fedora:34 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN dnf --enablerepo=updates-testing -y update RUN dnf --enablerepo=updates-testing -y install \ clang-analyzer \ codespell \ gcc \ git \ glib2-devel \ glibc-langpack-en \ gtk-doc \ lcov \ libgudev-devel \ meson-0.56.2-2.fc34 \ polkit-devel \ python3 \ python3-dbus \ python3-dbusmock \ python3-gobject \ rpm-build \ redhat-rpm-config \ systemd-devel \ umockdev-devel \ uncrustify RUN mkdir /src /build WORKDIR /src bolt-0.9.2/contrib/Dockerfile-ub1804000066400000000000000000000007741417453051000170450ustar00rootroot00000000000000## -*- mode: dockerfile -*- FROM ubuntu:18.04 ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update -q && apt-get install -yy \ asciidoc \ git \ gobject-introspection \ libgirepository1.0-dev \ libpolkit-gobject-1-dev \ locales \ libudev-dev \ libumockdev-dev \ libsystemd-dev \ meson \ pkg-config \ policykit-1 \ python3-dbus \ python3-dbusmock \ udev \ umockdev \ systemd \ && rm -rf /var/lib/apt/lists/* RUN mkdir /src WORKDIR /src bolt-0.9.2/contrib/PKGBUILD000066400000000000000000000014331417453051000152470ustar00rootroot00000000000000pkgname=bolt-git pkgver=r271.6d86460 pkgrel=1 pkgdesc="Thunderbolt 3 security system daemon" arch=('i686' 'x86_64') url="https://github.com/gicmo/bolt" license=('LGPL') depends=('polkit' 'systemd') makedepends=('asciidoc' 'meson' 'git') checkdepends=('umockdev') conflicts=('bolt') provides=('bolt') source=(git+https://github.com/gicmo/bolt.git) md5sums=('SKIP') pkgver() { cd bolt printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" } build() { cd "${srcdir}" install -d build arch-meson bolt build ninja -v -C build } check() { cd "${srcdir}" ninja -C build test } package() { cd "${srcdir}" DESTDIR="${pkgdir}" ninja -C build install # Fixup mode to match polkit install -d -o root -g 102 -m 750 "${pkgdir}/usr/share/polkit-1/rules.d" } bolt-0.9.2/contrib/bolt-mock000077500000000000000000000530721417453051000157460ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- # # boltd mocking # # Copyright © 2017 Red Hat, Inc # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . # Authors: # Christian J. Kellner from __future__ import print_function import argparse import atexit import os import readline import shlex import shutil import subprocess import sys import tempfile import time import uuid try: import gi from gi.repository import GLib from gi.repository import Gio gi.require_version('UMockdev', '1.0') from gi.repository import UMockdev except ImportError as e: sys.stderr.write('Missing dependencies: %s\n' % str(e)) sys.exit(1) DBUS_NAME = 'org.freedesktop.bolt' DBUS_PATH = '/org/freedesktop/bolt' DBUS_IFACE_PREFIX = 'org.freedesktop.bolt1.' DBUS_IFACE_MANAGER = DBUS_IFACE_PREFIX + 'Manager' DBUS_IFACE_DEVICE = DBUS_IFACE_PREFIX + 'Device' DBUS_IFACE_DOMAIN = DBUS_IFACE_PREFIX + 'Domain' SERVICE_FILE = '/usr/share/dbus-1/system-services/org.freedesktop.bolt.service' class SysfsDev: known_attrs = [] def __init__(self, sysname, syspath): self.sysname = sysname self.syspath = syspath @property def is_connected(self): return self.syspath is not None def sysattr_path(self, name): return os.path.join(self.syspath, name) def have_sysattr(self, name): path = self.sysattr_path(name) return os.path.isfile(path) def read_sysattr(self, name): path = self.sysattr_path(name) try: with open(path, 'r') as fd: raw = fd.read() return raw.strip() except FileNotFoundError: return None def dump(self, prefix='', file=sys.stdout): if self.sysname is None: print('%s disconnected' % prefix, file=file) else: print('%s%s @ %s' % (prefix, self.sysname, self.syspath), file=file) @staticmethod def bridge(klass): lst = getattr(klass, 'known_attrs', []) def install(attr): def getter(self): return self.read_sysattr(attr) prop = property(getter) setattr(klass, attr, prop) [install(attr) for attr in lst] return klass @SysfsDev.bridge class Domain(SysfsDev): known_attrs = ['boot_acl', 'security'] def __init__(self, index, sysname, syspath): super(Domain, self).__init__(sysname, syspath) self.index = index self.parent = None self.serial = 100 self.host = None @property def uid(self): return self.host and self.host.uid def gen_serial(self): s = self.serial self.serial += 1 return s def find_device(self, name_or_id): if self.host is None: return None return self.host.find_device(name_or_id) def show(self, prefix='', file=sys.stdout): name = self.sysname or 'domain' uid = self.host.uid if self.host else '' print('%s%s %s' % (prefix, name, uid), file=file) pf = prefix + ' ' super(Domain, self).dump(prefix=pf, file=file) security = self.security or 'unknown' print('%ssecurity %s' % (pf, security), file=file) @SysfsDev.bridge class Device(SysfsDev): known_attrs = ['authorized', 'boot', 'device', 'device_name', 'generation', 'key', 'unique_id', 'vendor', 'vendor_name'] search_fields = ('uid', 'sysname', 'device_name') def __init__(self, uid, sysname, syspath): super(Device, self).__init__(sysname, syspath) self.uid = uid self.domain = None self.is_host = False self.children = [] def find_device(self, identifier, fields=search_fields): for i in [getattr(self, f) for f in fields]: if i == identifier: return self for c in self.children: d = c.find_device(identifier) if d is not None: return d return None def show(self, prefix='', file=sys.stdout): print('%sdevice %s' % (prefix, self.uid), file=file) pf = prefix + ' ' super(Device, self).dump(prefix=pf, file=file) if not self.is_connected or self.is_host: return print('%sauthorized %s' % (pf, self.authorized), file=file) class MockSysfs: def __init__(self): self.testbed = UMockdev.Testbed.new() self.domains = {} @property def root(self): return self.testbed.get_root_dir() def _gen_domain_id(self): for i in range(1024): if ('domain%d' % i) not in self.domains: return i raise ValueError('too many domains in use') def find_domain(self, name_or_id): for name, dom in self.sysfs.domains.items(): if target in (name, dom.uid): return dom return None def find_device(self, name_or_id): for name, dom in self.domains.items(): d = dom.find_device(name_or_id) if d is not None: return d return None def domain_add(self, security, bootacl, iommu): index = self._gen_domain_id() sysname = 'domain%d' % index props = ['security', security] if isinstance(bootacl, int): bootacl = ',' * (bootacl - 1) if bootacl is not None: props += ['boot_acl', bootacl] if iommu is not None: props += ['iommu_dma_protection', str(iommu) + '\n'] syspath = self.testbed.add_device('thunderbolt', sysname, None, props, ['DEVTYPE', 'thunderbolt_domain']) domain = Domain(index, sysname, syspath) self.domains[sysname] = domain return domain def host_add(self, domain, uid, name, vendor, generation): sysname = "%d-0" % domain.index attributes = ['device_name', name, 'device', '0x23', 'vendor_name', vendor, 'vendor', '0x23', 'authorized', '1', 'unique_id', uid] if generation is not None: attributes += ['generation', str(generation)] syspath = self.testbed.add_device('thunderbolt', sysname, domain.syspath, attributes, ['DEVTYPE', 'thunderbolt_device']) host = Device(uid, sysname, syspath) host.is_host = True domain.host = host host.domain = domain return host def device_add(self, parent, uid, name, vendor, authorized, key, boot, gen): domain = parent.domain serial = domain.gen_serial() sysname = "%d-%d" % (domain.index, serial) props = ['device_name', name, 'device', '0x23', 'vendor_name', vendor, 'vendor', '0x23', 'authorized', '%d\n' % authorized, 'unique_id', uid] if key is not None: # The kernel always returns the key with trailing `\n` if not key.endswith('\n'): key += '\n' props += ['key', key] if boot is not None: props += ['boot', str(boot)] if gen is not None: props += ['generation', str(gen)] syspath = self.testbed.add_device('thunderbolt', sysname, parent.syspath, props, ['DEVTYPE', 'thunderbolt_device']) device = Device(uid, sysname, syspath) device.domain = domain parent.children.append(device) return device def remove(self, dev): self.testbed.uevent(dev.syspath, "remove") self.testbed.remove_device(dev.syspath) dev.syspath = None dev.sysname = None class Store: def __init__(self, path=None): self.path = path self.mocked = False if self.path is None: self.make_mock_store() def __del__(self): if self.mocked: shutil.rmtree(self.path) def make_mock_store(self): path = tempfile.mkdtemp() os.makedirs(os.path.join(path, 'devices')) os.makedirs(os.path.join(path, 'domains')) os.makedirs(os.path.join(path, 'keys')) self.path = path self.mocked = True class Daemon: def __init__(self, store, sysfs): self._discover_binary() self.rundir = tempfile.mkdtemp() self.store = store self.sysfs = sysfs self.log = None self._boltd = None def __del__(self): if self.is_running: self.stop() shutil.rmtree(self.rundir) @staticmethod def path_from_service_file(sf): with open(sf) as f: for line in f: if not line.startswith('Exec='): continue return line.split('=', 1)[1].strip() return None def _discover_binary(self): if 'BOLT_BUILD_DIR' in os.environ: print('Using boltd from local build') build_dir = os.environ['BOLT_BUILD_DIR'] boltd = os.path.join(build_dir, 'boltd') boltctl = os.path.join(build_dir, 'boltctl') elif 'UNDER_JHBUILD' in os.environ: print('Using boltd from JHBuild') jhbuild_prefix = os.environ['JHBUILD_PREFIX'] boltd = os.path.join(jhbuild_prefix, 'libexec', 'boltd') boltctl = os.path.join(jhbuild_prefix, 'bin', 'boltctl') elif os.path.exists(os.path.abspath('build/boltd')): build_dir = os.path.abspath('build') print('Using boltd from %s' % build_dir) boltd = os.path.join(build_dir, 'boltd') boltctl = os.path.join(build_dir, 'boltctl') else: print('Using boltd from system installation') boltd = Daemon.path_from_service_file(SERVICE_FILE) boltctl = shutil.which('boltctl') assert boltd is not None, 'failed to find daemon' assert os.access(boltctl, os.X_OK), "could not execute @ " + boltctl self.paths = {'daemon': boltd, 'boltctl': boltctl} self.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) # dbus helper methods def _get_dbus_property(self, name, interface=DBUS_IFACE_MANAGER): proxy = Gio.DBusProxy.new_sync(self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, DBUS_NAME, DBUS_PATH, 'org.freedesktop.DBus.Properties', None) return proxy.Get('(ss)', interface, name) def start(self, args): timeout = 10 env = os.environ.copy() env['G_DEBUG'] = 'fatal-criticals' env['UMOCKDEV_DIR'] = self.sysfs.root env['STATE_DIRECTORY'] = self.store.path env['RUNTIME_DIRECTORY'] = self.rundir argv = [self.paths['daemon'], '--replace'] if args.verbose: argv += ['-v'] self._boltd = subprocess.Popen(argv, env=env, stdout=self.log, stderr=subprocess.STDOUT) timeout_count = timeout * 10 timeout_sleep = 0.1 while timeout_count > 0: time.sleep(timeout_sleep) timeout_count -= 1 try: self._get_dbus_property('Version') break except GLib.GError: pass else: timeout_time = timeout * 10 * timeout_sleep print('daemon did not start in %d seconds' % timeout_time) self._boltd = None return if self._boltd.poll() is not None: print('daemon crashed :(') self._boltd = None def stop(self): if self._boltd: try: self._boltd.terminate() except OSError: pass self._boltd.wait() self._boltd = None @property def is_running(self): return self._boltd is not None class Command: class SubCommand: def __init__(self, name, desc, parser, handler): self.name = name self.desc = desc self.parser = parser self.handler = handler def __call__(self, obj, argv, parser): if argv.show_help: print(self.parser.format_help()) else: self.handler(obj, argv, self.parser) def __init__(self, handler): self.handler = handler self.name = handler.__name__ self.desc = handler.__doc__ or self.name self.parser = argparse.ArgumentParser(prog=self.name, description=self.desc, add_help=False) self.parser.add_argument('--help', dest='show_help', action='store_true') self.subparsers = None self.use_parse_known = False def __call__(self, obj, argv): if self.use_parse_known: args, rest = self.parser.parse_known_args(argv) else: args = self.parser.parse_args(argv) rest = None context = {'parser': self.parser, 'unknown': rest} if args.show_help: print(self.parser.format_help()) elif hasattr(args, 'func'): args.func(obj, args, context) else: self.handler(obj, args, context) def subcommand(self, handler): if self.subparsers is None: self.subparsers = self.parser.add_subparsers() name = handler.__name__ if not name.startswith(self.name + '_'): raise ValueError('Invalid naming scheme') name = name[len(self.name + '_'):] desc = handler.__doc__ or self.name p = self.subparsers.add_parser(name, help=desc, add_help=False) p.add_argument('--help', dest='show_help', action='store_true') cmd = Command.SubCommand(name, desc, p, handler) p.set_defaults(func=cmd) return cmd @staticmethod def subcommand_dispatch(obj, args): args.func(obj, args) def command(func): cmd = Command(func) return cmd def arg(name_or_flags, *args, **kwargs): def decorator(func): func.parser.add_argument(name_or_flags, *args, **kwargs) return func return decorator def parse_known(func): func.use_parse_known = True return func def collect_args(klass): methods = [getattr(klass, m) for m in dir(klass) if not m.startswith('__')] commands = [m for m in methods if isinstance(m, Command)] klass.commands = {m.name: m for m in commands} return klass @collect_args class World: def __init__(self, store, sysfs, daemon): self.store = store self.sysfs = sysfs self.daemon = daemon def handle_line(self, line): if not line: return True if line == 'exit': return False if line.startswith('#'): return True argv = shlex.split(line) idx = [i for i, arg in enumerate(argv) if arg.startswith('#')] if len(idx): argv = argv[:idx[0]] cmd = self.commands.get(argv[0], None) if cmd is None: print('unknown command') return True try: cmd(self, argv[1:]) except SystemExit: return True return True def loop(self): do_loop = True while do_loop: try: line = input('> ') except EOFError: print('Bye.') break do_loop = self.handle_line(line) @command def help(self, args, context): for name, cmd in self.commands.items(): print('%s - %s ' % (name, cmd.desc)) @arg('--verbose', action='store_true') @command def start(self, args, context): '''start the bolt daemon''' if self.daemon.is_running: print('boltd already running') else: print('starting boltd') self.daemon.start(args) @command def stop(self, args, context): '''stops the bolt daemon''' if not self.daemon.is_running: print('boltd not running') else: print('stopping boltd') self.daemon.stop() @command def status(self, args, context): '''Show overall status''' print('sysfs: %s' % self.sysfs.root) print('store: %s' % self.store.path) print('boltd: %s' % self.daemon.paths['daemon']) print('rundir: %s' % self.daemon.rundir) print('boltd %s' % ('running' if self.daemon.is_running else 'stopped')) @arg('file', type=argparse.FileType('r'), default=None) @command def load(self, args, context): '''Load and execute a commands from a file''' for line in args.file: line = line.strip() print('@ %s' % line) self.handle_line(line) @command def controller(self, args, context): '''control controllers. hah!''' parser = context['parser'] print(parser.format_help()) @arg('--security', type=str, default='secure') @arg('--uuid', type=str, default=None) @arg('--iommu', type=str, choices=[None, '0', '1'], default=None) @arg('--bootacl', type=str, default=None) @arg('--generation', type=int, default=3) @arg('--vendor', type=str, default='GNOME.org') @arg('--name', type=str, default='Laptop') @controller.subcommand def controller_new(self, args, context): '''create a new domain+host combination''' security = args.security bootacl = args.bootacl iommu = args.iommu gen = args.generation uid = args.uuid or str(uuid.uuid4()) print(uid) domain = self.sysfs.domain_add(security, bootacl, iommu) domain.show() name = args.name vendor = args.vendor host = self.sysfs.host_add(domain, uid, name, vendor, gen) host.show() @arg('id', type=str, help='name or uuid') @controller.subcommand def controller_rm(self, args, parser): '''removes a new domain+host combination''' target = args.id domain = self.sysfs.find_domain(target) if domain is None: print('Could not find controller') return if domain.host is not None: self.sysfs.remove(domain.host) self.sysfs.remove(domain) @command def device(self, args, parser): '''control devices''' print(parser.format_help()) @arg('--key', type=str, default=None) @arg('--boot', type=int, choices=[None, 0, 1], default=None) @arg('--authorized', type=int, choices=[0, 1], default=0) @arg('--uuid', type=str, default=None) @arg('--vendor', type=str, default='GNOME.org') @arg('--generation', type=int, default=3) @arg('name', type=str) @arg('parent', type=str) @device.subcommand def device_new(self, args, context): '''create a new device combination''' parent = self.sysfs.find_device(args.parent) if parent is None: print('unknown parent') return uid = args.uuid or str(uuid.uuid4()) device = self.sysfs.device_add(parent, uid, args.name, args.vendor, args.authorized, args.key, args.boot, args.generation) device.show() @arg('id', type=str, help='name or uuid') @device.subcommand def device_rm(self, args, context): '''removes a device''' device = self.sysfs.find_device(args.id) if device is None: print('unknown device') return print('disconnecting %s' % device.uid) self.sysfs.remove(device) @parse_known @command def boltctl(self, args, context): '''Invoke boltctl''' boltctl = self.daemon.paths['boltctl'] bc_args = context['unknown'] or [] argv = [boltctl] + bc_args subprocess.call(argv) def main(): readline.set_history_length(1000) histfile = os.path.join(os.path.expanduser("~"), ".cache", "bolt_mock") try: readline.read_history_file(histfile) except FileNotFoundError: pass atexit.register(readline.write_history_file, histfile) store = Store() sysfs = MockSysfs() daemon = Daemon(store, sysfs) ctrl = World(store, sysfs, daemon) ctrl.loop() if __name__ == '__main__': if 'umockdev' not in os.environ.get('LD_PRELOAD', ''): wrapped = ['umockdev-wrapper'] + sys.argv os.execvp(wrapped[0], wrapped) main() bolt-0.9.2/contrib/bolt.spec.in000066400000000000000000000052601417453051000163460ustar00rootroot00000000000000Name: bolt Version: @version@ Release: 0.@reltag@%{?dist} Summary: Thunderbolt device manager License: LGPLv2+ URL: https://gitlab.freedesktop.org/bolt/bolt Source0: %{url}/-/archive/%{version}/%{name}-%{version}.tar.xz BuildRequires: gcc BuildRequires: asciidoc BuildRequires: meson BuildRequires: libudev-devel BuildRequires: pkgconfig(gio-2.0) BuildRequires: pkgconfig(libudev) BuildRequires: pkgconfig(systemd) BuildRequires: pkgconfig(libsystemd) BuildRequires: polkit-devel BuildRequires: systemd %{?systemd_requires} # for the integration test (optional) %if 0%{?fedora} BuildRequires: pygobject3-devel BuildRequires: python3-dbus BuildRequires: python3-dbusmock BuildRequires: umockdev-devel %endif %description bolt is a system daemon to manage Thunderbolt devices via a D-BUS API. Thunderbolt 3 introduced different security modes that require devices to be authorized before they can be used. The D-Bus API can be used to list devices, enroll them (authorize and store them in the local database) and forget them again (remove previously enrolled devices). It also emits signals if new devices are connected (or removed). During enrollment devices can be set to be automatically authorized as soon as they are connected. A command line tool, called boltctl, can be used to control the daemon and perform all the above mentioned tasks. %package tests Summary: Integration tests, unit tests and bolt-mock test helper Requires: %{name}%{?_isa} = %{version}-%{release} License: LGPLv2+ %description tests Contains the unit tests suite, the integration tests and a bolt-mock helper that can be used to run the boltd daemon in a mock environment where thunderbolt domains and devices can be simulated. %prep %setup -q %build %meson -Ddb-name=@dbname@ -Dinstall-tests=true %meson_build %check %meson_test %install %meson_install %post %systemd_post %{name}.service %preun %systemd_preun %{name}.service %postun %systemd_postun_with_restart %{name}.service %files %license COPYING %doc README.md CHANGELOG.md BUGS.md INSTALL.md HACKING.md %{_bindir}/boltctl %{_libexecdir}/boltd %{_unitdir}/%{name}.service %{_udevrulesdir}/*-%{name}.rules %{_datadir}/dbus-1/system.d/org.freedesktop.bolt.conf %{_datadir}/dbus-1/interfaces/org.freedesktop.bolt.xml %{_datadir}/polkit-1/actions/org.freedesktop.bolt.policy %{_datadir}/polkit-1/rules.d/org.freedesktop.bolt.rules %{_datadir}/dbus-1/system-services/org.freedesktop.bolt.service %{_mandir}/man1/boltctl.1* %{_mandir}/man8/boltd.8* %ghost %dir @dbdir@ %files tests %{_libexecdir}/installed-tests/bolt %{_libexecdir}/installed-tests/bolt/* %changelog * @longdate@ Christian Kellner - @version@-@reltag@ - build from git sources. bolt-0.9.2/contrib/cov-model.c000066400000000000000000000013671417453051000161620ustar00rootroot00000000000000/* GLib types. */ typedef size_t gsize; typedef char gchar; typedef unsigned char guchar; typedef int gint; typedef unsigned long gulong; typedef unsigned int guint32; typedef void * gpointer; typedef unsigned int gboolean; void g_assertion_message_expr (const char *domain, const char *file, int line, const char *func, const char *expr) { __coverity_panic__ (); } #define g_critical(...) __coverity_panic__ (); /* Treat it as a memory sink to hide one-time allocation leaks. */ void (g_once_init_leave) (volatile void *location, gsize result) { __coverity_escape__ (result); } bolt-0.9.2/contrib/coverity.sh000077500000000000000000000012011417453051000163170ustar00rootroot00000000000000#!/bin/bash set -e set -x GIT_DESC=$(git describe) env # prepare export LC_ALL=C.UTF-8 export PYTHONPATH="/usr/share/glib-2.0" rm -rf /build/* # info ls -la /build whoami # actual building export CC=clang CXX=clang meson -Dcoverity=true /build cov-build --dir /build/cov-int ninja -C /build pushd /build tar -caf coverity.xz cov-int popd if [[ -v COVERITY_TOKEN && -v COVERITY_EMAIL ]]; then curl --form "token=${COVERITY_TOKEN}" \ --form "email=${COVERITY_EMAIL}" \ --form "file=@/build/coverity.xz" \ --form "version=main" \ --form "description=${GIT_DESC}" \ https://scan.coverity.com/builds?project=gicmo%2Fbolt fi bolt-0.9.2/contrib/docker-build.sh000077500000000000000000000014271417453051000170310ustar00rootroot00000000000000#!/bin/bash set -e set -x # prepare export LC_ALL=C.UTF-8 export PYTHONPATH="/usr/share/glib-2.0" rm -rf /build/* # info ls -la /build whoami # actual building meson -Db_coverage=true -Dtests-speed=slow . /build ninja -C /build meson test -C /build --verbose if [[ -x "$(command -v lcov)" ]]; then ninja -C /build coverage fi if [[ -x "$(command -v scan-build)" ]]; then ninja -C /build scan-build if [[ -n "$(ls -A /build/meson-logs/scanbuild/)" ]]; then echo "Scan build log found, assuming defects exist" exit 1 fi fi if [[ -x "$(command -v lcov)" ]]; then scripts/uncrustify.sh --check fi if [[ -x $(command -v pylint) ]]; then pylint tests/test-integration fi if [[ -x $(command -v codespell) ]]; then codespell -S .git -S build fi bolt-0.9.2/contrib/js/000077500000000000000000000000001417453051000145365ustar00rootroot00000000000000bolt-0.9.2/contrib/js/.jshintrc000066400000000000000000000132321417453051000163640ustar00rootroot00000000000000{ "maxerr" : 50, // {int} Maximum error before stopping // Enforcing "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) "camelcase" : false, // true: Identifiers must be in camelCase "curly" : false, // true: Require {} for every new block or scope "eqeqeq" : true, // true: Require triple equals (===) for comparison "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` "indent" : 4, // {int} Number of spaces to use for indentation "latedef" : "nofunc", // true: Require variables/functions to be defined before being used "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` "noempty" : true, // true: Prohibit use of empty blocks "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) "plusplus" : false, // true: Prohibit use of `++` & `--` "quotmark" : false, // Quotation mark consistency: // false : do nothing (default) // true : ensure whatever is used is consistent // "single" : require single quotes // "double" : require double quotes "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) "unused" : false, // true: Require all defined variables be used "strict" : false, // true: Requires all functions run in ES5 Strict Mode "trailing" : true, // true: Prohibit trailing whitespaces "maxparams" : false, // {int} Max number of formal params allowed per function "maxdepth" : false, // {int} Max depth of nested blocks (within functions) "maxstatements" : false, // {int} Max number statements per function "maxcomplexity" : false, // {int} Max cyclomatic complexity per function "maxlen" : false, // {int} Max number of characters per line // Relaxing "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) "boss" : false, // true: Tolerate assignments where comparisons would be expected "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. "eqnull" : false, // true: Tolerate use of `== null` "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) "moz" : true, // true: Allow Mozilla specific syntax (extends and overrides esnext features) // (ex: `for each`, multiple try/catch, function expression…) "evil" : false, // true: Tolerate use of `eval` and `new Function()` "expr" : false, // true: Tolerate `ExpressionStatement` as Programs "funcscope" : false, // true: Tolerate defining variables inside control statements" "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') "iterator" : false, // true: Tolerate using the `__iterator__` property "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block "laxbreak" : true, // true: Tolerate possibly unsafe line breakings "laxcomma" : false, // true: Tolerate comma-first style coding "loopfunc" : false, // true: Tolerate functions being defined in loops "multistr" : true, // true: Tolerate multi-line strings "proto" : false, // true: Tolerate using the `__proto__` property "scripturl" : false, // true: Tolerate script-targeted URLs "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` "validthis" : false, // true: Tolerate using this in a non-constructor function // Environments "browser" : false, // Web Browser (window, document, etc) "couch" : false, // CouchDB "devel" : false, // Development/debugging (alert, confirm, etc) "dojo" : false, // Dojo Toolkit "jquery" : false, // jQuery "mootools" : false, // MooTools "node" : false, // Node.js "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) "prototypejs" : false, // Prototype and Scriptaculous "rhino" : false, // Rhino "worker" : false, // Web Workers "wsh" : false, // Windows Scripting Host "yui" : false, // Yahoo User Interface // Legacy "nomen" : false, // true: Prohibit dangling `_` in variables "onevar" : false, // true: Allow only one `var` statement per function "passfail" : false, // true: Stop on first error "white" : false, // true: Check against strict whitespace and indentation rules // Custom Globals "predef" : [ "log", "logError", "print", "printerr", "imports", "ARGV", "pkg", "_", "C_", "N_" ] } bolt-0.9.2/contrib/js/client.js000066400000000000000000000213731417453051000163600ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Lang = imports.lang; const Signals = imports.signals; /* Keep in sync with data/org.freedesktop.bolt.xml */ const BoltClientInterface = ' \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const BoltDeviceInterface = ' \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const BoltClientProxy = Gio.DBusProxy.makeProxyWrapper(BoltClientInterface); const BoltDeviceProxy = Gio.DBusProxy.makeProxyWrapper(BoltDeviceInterface); /* */ var Status = { DISCONNECTED: 0, CONNECTED: 1, AUTHORIZING: 2, AUTH_ERROR: 3, AUTHORIZED: 4, AUTHORIZED_SECURE: 5, AUTHORIZED_NEWKY: 6 }; var Policy = { DEFAULT: 0, MANUAL: 1, AUTO:2 }; var AuthFlags = { NONE: 0, }; const BOLT_DBUS_NAME = 'org.freedesktop.bolt'; const BOLT_DBUS_PATH = '/org/freedesktop/bolt'; var Client = new Lang.Class({ Name: 'BoltClient', _init: function(readyCallback) { this._readyCallback = readyCallback; this._proxy = new BoltClientProxy( Gio.DBus.system, BOLT_DBUS_NAME, BOLT_DBUS_PATH, Lang.bind(this, this._onProxyReady) ); this._signals = []; this.probing = false; }, _onProxyReady: function(proxy, error) { if (error !== null) { log(error.message); return; } this._proxy = proxy; this._proxyConnect('g-properties-changed', Lang.bind(this, this._onPropertiesChanged)); this._proxyConnect('DeviceAdded', Lang.bind(this, this._onDeviceAdded), true); this.probing = this._proxy.Probing; if (this.probing) this.emit('probing-changed', this.probing); this._readyCallback(this); }, _onPropertiesChanged: function(proxy, properties) { let unpacked = properties.deep_unpack(); if (!('Probing' in unpacked)) return; this.probing = this._proxy.Probing; this.emit('probing-changed', this.probing); }, _onDeviceAdded: function(proxy, emitter, params) { let [path] = params; let device = new BoltDeviceProxy(Gio.DBus.system, BOLT_DBUS_NAME, path); this.emit('device-added', device); }, _proxyConnect: function(name, callback, dbus) { var signal_id; if (dbus === true) signal_id = this._proxy.connectSignal(name, Lang.bind(this, callback)); else signal_id = this._proxy.connect(name, Lang.bind(this, callback)); this._signals.push([dbus, signal_id]); }, _proxyDisconnectAll: function() { while (this._signals.length) { let [dbus, sid] = this._signals.shift(); if (dbus === true) this._proxy.disconnectSignal(sid); else this._proxy.disconnect(sid); } }, /* public methods */ close: function() { this._proxyDisconnectAll(); this._proxy = null; }, listDevices: function(callback) { this._proxy.ListDevicesRemote(Lang.bind(this, function (res, error) { if (error) { callback(null, error); return; } let [paths] = res; let devices = []; for (let i = 0; i < paths.length; i++) { let path = paths[i]; let device = new BoltDeviceProxy(Gio.DBus.system, BOLT_DBUS_NAME, path); devices.push(device); } callback(devices, null); })); }, enrollDevice: function(id, policy, callback) { this._proxy.EnrollDeviceRemote(id, policy, AuthFlags.NONE, Lang.bind(this, function (res, error) { if (error) { callback(null, error); return; } let [path] = res; let device = new BoltDeviceProxy(Gio.DBus.system, BOLT_DBUS_NAME, path); callback(device, null); })); }, deviceGetParent: function(dev, callback) { let parentUid = dev.Parent; if (!parentUid) { callback (null, dev, null); return; } this._proxy.DeviceByUidRemote(dev.Parent, Lang.bind(this, function (res, error) { if (error) { callback(null, dev, error); return; } let [path] = res; let parent = new BoltDeviceProxy(Gio.DBus.system, BOLT_DBUS_NAME, path); callback(parent, dev, null); })); }, }); Signals.addSignalMethods(Client.prototype); /* helper class to automatically authorize new devices */ var AuthRobot = new Lang.Class({ Name: 'BoltAuthRobot', _init: function(client) { this._client = client; this._devicesToEnroll = []; this._enrolling = false; this._client.connect('device-added', Lang.bind(this, this._onDeviceAdded)); }, close: function() { this.disconnectAll(); this._client = null; }, /* the "device-added" signal will be emitted by boltd for every * device that is not currently stored in the database. We are * only interested in those devices, because all known devices * will be handled by the user himself */ _onDeviceAdded: function(cli, dev) { if (dev.Status !== Status.CONNECTED) { return; } /* check if we should enroll the device */ let res = [false]; this.emit('enroll-device', dev, res); if (res[0] !== true) { return; } /* ok, we should authorize the device, add it to the back * of the list */ this._devicesToEnroll.push(dev); this._enrollDevices(); }, /* The enrollment queue: * - new devices will be added to the end of the array. * - an idle callback will be scheduled that will keep * calling itself as long as there a devices to be * enrolled. */ _enrollDevices: function() { if (this._enrolling) { return; } this.enrolling = true; GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._enrollDevicesIdle)); }, _onEnrollDone: function(device, error) { if (error) { this.emit('enroll-failed', error, device); } /* TODO: scan the list of devices to be authorized for children * of this device and remove them (and their children and * their children and ....) from the device queue */ this._enrolling = this._devicesToEnroll.length > 0; if (this._enrolling) { GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._enrollDevicesIdle)); } }, _enrollDevicesIdle: function() { let devices = this._devicesToEnroll; let dev = devices.shift(); if (dev === undefined) { return GLib.SOURCE_REMOVE; } this._client.enrollDevice(dev.Uid, Policy.DEFAULT, Lang.bind(this, this._onEnrollDone)); return GLib.SOURCE_REMOVE; }, }); Signals.addSignalMethods(AuthRobot.prototype); bolt-0.9.2/contrib/js/test.js000066400000000000000000000027741417453051000160650ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Lang = imports.lang; imports.searchPath.unshift('.'); const Bolt = imports.client; let gotParent = function (p, d, e) { if (e) { log(e); return; } if (!p) { print (' device: '+ d.Uid + ' -> no parent'); return; } print (' device: '+ d.Uid + ' -> parent: ' + p.Uid); }; let client = new Bolt.Client(function (client) { client.listDevices(function (devices, error) { if (error) { print ('error ' + error); return; } for (let i = 0; i < devices.length; i++) { let d = devices[i]; print(' ' + d.Uid + " " + d.Name); client.deviceGetParent(d, gotParent); } }); }); let loop = new GLib.MainLoop(null, false); loop.run(); bolt-0.9.2/data/000077500000000000000000000000001417453051000133735ustar00rootroot00000000000000bolt-0.9.2/data/90-bolt.rules000066400000000000000000000005471417453051000156430ustar00rootroot00000000000000# bolt udev rules # # SPDX-License-Identifier: GPL-2.0-or-later # # Copyright © 2017 Red Hat, Inc # # Authors: # Christian J. Kellner # ACTION=="remove", GOTO="bolt_end" # start bolt service if we have a thunderbolt device connected SUBSYSTEM=="thunderbolt", TAG+="systemd", ENV{SYSTEMD_WANTS}+="bolt.service" LABEL="bolt_end" bolt-0.9.2/data/bolt.service.in000066400000000000000000000012011417453051000163140ustar00rootroot00000000000000[Unit] Description=Thunderbolt system service After=polkit.service Documentation=man:boltd(8) [Service] Type=dbus BusName=org.freedesktop.bolt ExecStart=@libexecdir@/boltd #Environment="G_MESSAGES_DEBUG=all" Restart=on-failure NotifyAccess=main WatchdogSec=3min MemoryDenyWriteExecute=yes PrivateTmp=yes ProtectControlGroups=yes ProtectHome=yes ProtectKernelModules=yes ProtectSystem=full RestrictAddressFamilies=AF_NETLINK AF_UNIX RestrictRealtime=yes ReadWritePaths=@dbdir@ SystemCallFilter=~@mount CapabilityBoundingSet=CAP_NET_ADMIN #directory management RuntimeDirectory=@dbname@ RuntimeDirectoryPreserve=yes StateDirectory=@dbname@ bolt-0.9.2/data/dbus.gresource.xml000066400000000000000000000004031417453051000170440ustar00rootroot00000000000000 org.freedesktop.bolt.xml bolt-0.9.2/data/org.freedesktop.bolt.conf000066400000000000000000000021421417453051000203010ustar00rootroot00000000000000 bolt-0.9.2/data/org.freedesktop.bolt.service.in000066400000000000000000000001501417453051000214160ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.bolt Exec=@libexecdir@/boltd User=root SystemdService=bolt.service bolt-0.9.2/data/org.freedesktop.bolt.xml000066400000000000000000000375451417453051000201730ustar00rootroot00000000000000 Thunderbolt device management. Version of the daemon. Indication that a new thunderbolt device has been connected and the manager is reacting to that. Probing should be true as long as the new device (and any possible attached peripherals) are initialized by the system. The policy to use during enrollment when "default" was specified. The security level of the system. The authorization mode of the daemon. Currently supported values are "enabled" if it is authorizing devices or "disabled" if authorization is disabled. State of the ForcePower setting of the bolt daemon. The maximum generation of any of Thunderbolt controller associated with the device. It will contain 4 for USB4, 3 for Thunderbolt 3. An array of object paths for the domains. List all active thunderbolt domains. The id of the domain. Object path for the domain. Return a domain given its identifier. An array of object paths for the devices. List all known devices, i.e. connected or stored in the database. The unique id of the device. Object path for the devices. Return a device given its unique identifier. The unique id of the device. Policy to use for the device. Control aspects of enrollment. Object path for the devices. Authorize a device, and on success, store the device in the database. If policy is set to "Auto", the device will be automatically authorized in the future. The unique id of the device. Remove the device and any associated information, such as the policy and its key, from the store. Object path of the new device. A new device was added. Object path of the removed device. A device was removed. Object path of the domain. A thunderbolt domain was added. Object path of the removed domain. A thunderbolt domain was removed. Thunderbolt force power management. Is there system level support for force powering the thunderbolt controller. State of the ForcePower setting of the bolt daemon. The minimum amount of time the controller will be powered. This timeout will get reset with every new activity on the thundrbolt bus while the controller is being force powered. Who is requesting the force power op. Control aspects of the force power operation. File descriptor reference to the guard. Force power the thunderbolt controller, if supported. Array of guard information, containing the "id", "who" and the pid of the guard. List active internal and external power guards. Representation of a single Thunderbolt device. The unique-id of the device. The name of the device. The vendor of the device. The type of the device, i.e. 'host' or 'peripheral' The generation of the Thunderbolt controller associated with the device. It will contain 4 for USB4, 3 for Thunderbolt 3. The current status of the device. Details of authorization information, including if they device was securily authorized via key verification ('secure') or does not support this method ('nokey'). The flag 'boot' indicates that the devices was already authorized by the firmware during pre-boot. If no downstream PCIe tunnels are authorized the 'nopcie' flag will be set. The unique id of the parent the device. The only device without a parent will be the device that represents the host controller. The sysfs path of the device, if it is connected. When a device is connected, it always is connected via a thunderbolt domain, directly or via a chain of other devices. This is the identifier of the domain. Indication if the device is stored. The authorization policy of the device. If a key is associated with the device. If set, a name that was given to the device by the user. Can only be set when device is stored. Point in time (since Epoch, in seconds) when the device was connected (0 if it is not connected). Point in time (since Epoch, in seconds) when the device was authorized (0 if it is not authorized). Point in time (since Epoch, in seconds) when the device was stored (0 if it is not stored). Information about the link speed, i.e. with how many lanes the device is connected to its parent and what rate (in Gb/s) the lanes have. Separate information is reported for transmission (tx) and reception (rx). All lanes in one direction have the same rate. Control aspects of authorization. Authorize the device. Representation of a Thunderbolt domain. The unique-id of the domain. The (sysfs) id of the domain. The sysfs path of the domain, if it is connected. The security level of the domain. The boot access control list. The input output memory management unit (IOMMU) can used on recent hardware in combination with recent kernel versions to protect against attacks, like direct memory access (DMA) attacks, at the hardware level. If this property is true than IOMMU hardware protection is active. bolt-0.9.2/docs/000077500000000000000000000000001417453051000134125ustar00rootroot00000000000000bolt-0.9.2/docs/boltctl.1.txt000066400000000000000000000140551417453051000157620ustar00rootroot00000000000000boltctl(1) ========== NAME ---- boltctl - control the thunderbolt device manager SYNOPSIS -------- [verse] *boltctl* 'authorize' 'DEVICE' *boltctl* 'config' *boltctl* 'domains' *boltctl* 'enroll' 'DEVICE' *boltctl* 'forget' 'DEVICE' *boltctl* 'info' 'DEVICE' *boltctl* 'list' *boltctl* 'monitor' *boltctl* 'power' DESCRIPTION ------------ 'boltctl' is the command line interface to interact with 'boltd', the system daemon that manages Thunderbolt 3(TM) devices. It can be used to query the state of devices as well as manage them. Devices can be globally identified via their unique identifier (uuid). All commands that take a 'DEVICE' identifier expect this unique id. If no command is given, it is equivalent to 'boltctl list'. OPTIONS ------- *--version*:: Print version information and exit. *-U | --uuid {'full' | 'short' | 'alias' | N}*:: Control how UUIDs are printed. Since they are somewhat sensitive data it is not advisable to share them publicly in full length. Instead 'short' or 'alias' can and should be used when sharing the output of 'boltctl'. 'full';; Print all UUIDs in full length. 'short';; Truncate all UUIDs so only the first 13 characters are printed. 'alias';; All UUIDs are replaced by a random string that is derived from the UUID, therefore the devices can be uniquely identified without revealing the original UUID. N;; If a integer 'N' is specified, all UUIDs are truncated to only show up to 'N'. COMMANDS -------- authorize [-F | --first-time] 'DEVICE' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Authorize a currently unauthorized device identified via its unique id (uuid) 'DEVICE'. If a key is stored in the database it will be used, given the security level of the domain supports secure device connection. Use 'boltctl list' to find out the uuid of a device. *-F | --first-time*:: Normally, when attempting to authorize an already authorized device *boltctl* will do nothing and return a successful status code. When using this option, the attempt will fail and result in a negative exit code if the device is already authorized. config --describe [global|domain|device] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ List global, domain, or all (if nothing is specified) properties. The format is 3 columns: permission, name, description. Permission indicates if the property is only readable or can also be written. config 'KEY' ['VALUE'] ~~~~~~~~~~~~~~~~~~~~~~~ Get or set, if 'VALUE' is specified, a global property. config '.KEY' 'TARGET' ['VALUE'] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Get or set, if 'VALUE' is specified, a domain or device property, where 'TARGET' is the unique id of the domain or the device. domains [-v | --verbose] ~~~~~~~~~~~~~~~~~~~~~~~~ List all currently active Thunderbolt domains. A Thunderbolt domain represents the Thunderbolt controller hardware. There will be one domain (and host device) for each Thunderbolt controller present in the system. The 'security' property shows the security level of the controller. If 'iommu' support is active (see the *boltd* man page) it will be indicated by a '+iommu' suffix for "secure" or "user" mode, or just plain 'iommu' in case the security level is "none" (sl0). 'bootacl' shows the used and total slots of the boot access control list (BootACL) and the content of all non-empty entries. NB: if BootACL is unsupported it will show 0 for both (0/0). The 'online' property shows if the thunderbolt controller is currently powered by the firmware. *NB*: if the controller is currently offline the BootACL list will reflect what 'boltd' estimates the list will look like once the controller is back online and local changes have been synchronized to the controller. This might not be accurate if the list was modified in the meantime, e.g. from a different installation or OS. enroll [--policy 'policy'] 'DEVICE' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Authorize and record the device with the unique id 'DEVICE' in the database. If the domain supports secure connection a new key will be generated and stored in the database alongside the device name and vendor name. The key, if created, will be used in the future to securely authorize the device. *--policy {'default' | 'auto' | 'manual'}*:: Specify the policy to be used for the newly enrolled device. 'default';; Use the global default policy of the daemon; this can be changed, but is normally also 'auto'. 'auto';; Automatically authorize this device whenever it is connected. 'manual';; Do *not* automatically authorize the device; instead require manual authorization via *boltctl authorize*. forget 'DEVICE' ~~~~~~~~~~~~~~~ Remove the information about the device with the unique id 'DEVICE' from the database. This includes the key, if one was previously generated. If you pass '--all' instead of the 'DEVICE' all devices are removed instead of just one. info 'DEVICE' ~~~~~~~~~~~~ Display information about the device with the unique id 'DEVICE'. list [-a | --all] ~~~~~~~~~~~~~~~~~ List and print information about all connected and stored devices. *-a | --all*:: Normally, the only the device type that will be shown is peripherals. Therefore the device that represents the host itself will be omitted. Using this option will instead include all device types in the list. monitor ~~~~~~~ Listen for and show changes in connected devices. power [-t | --timeout 'seconds'] [-q | --query] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Power up the Thunderbolt controller. If the Thunderbolt controller is not in "native enumeration mode" it can be completely powered down by the host firmware/BIOS. On supported systems there is an interface to "force" power the thunderbolt controller. If supported this command will request the daemon to do so. The daemon will keep track of all client requests and will release the force power override when the last request is released. *-t | --timeout 'seconds'*:: Release the force power request after the specified amount of 'seconds' and exit. *-q | --query*:: Query the current force power status of the daemon. Author ------ Written by Christian Kellner . bolt-0.9.2/docs/boltd.8.txt000066400000000000000000000115541417453051000154330ustar00rootroot00000000000000boltd(8) ======== NAME ---- boltd - thunderbolt device managing system daemon SYNOPSIS -------- *boltd* ['OPTIONS'] DESCRIPTION ----------- boltd is the thunderbolt device manager daemon. Its goal is to enable the secure and convenient use of thunderbolt devices by using the security features of modern thunderbolt controllers. It provides the `org.freedesktop.bolt` name on the system bus. boltd is autostarted via systemd/udev if a thunderbolt device is connected. The thunderbolt I/O technology works by bridging PCIe between the controllers on each end of the connection, which in turn means that devices connected via Thunderbolt are ultimately connected via PCIe. Therefore thunderbolt can achieve very high connection speeds, fast enough to even drive external graphics cards. The downside is that it also makes certain attacks possible. To mitigate these security problems, the latest version -- known as Thunderbolt 3 -- supports different *security levels*: *none*:: No security. The behavior is identical to previous Thunderbolt versions. *dponly*:: No PCIe tunnels are created at all, but DisplayPort tunnels are allowed and will work. *user*:: Connected devices must be authorized by the user. Only then will the PCIe tunnels be activated. *secure*:: Basically the same as user mode, but additionally a key will be written to the device the first time the device is connected. This key will then be used to verify the identity of the connected device. *usbonly*:: One PCIe tunnel is created to a usb controller in a thunderbolt dock; no other downstream PCIe tunnels are authorized (needs 4.17 kernel and recent hardware). The primary task of *boltd* is to authorize thunderbolt peripherals if the security level is either `user` or `secure`. It provides a D-Bus API to list devices, enroll them (authorize and store them in the local database) and forget them again (remove previously enrolled devices). It also emits signals if new devices are connected (or removed). During enrollment devices can be set to be automatically authorized as soon as they are connected. A command line tool, called boltctl(1), can be used to control the daemon and perform all the above mentioned tasks. The pre-boot access control list (*BootACL*) feature is active when supported by the firmware and when 'boltd' is running on a new enough Linux kernel (>= 4.17). The 'BootACL' is a list of UUIDs, that can be written to the thunderbolt controller. If enabled in the BIOS, all devices in that list will be authorized by the firmware during pre-boot, which means these devices can be used in the BIOS setup and also during Linux early boot. NB: *no device verification* is done, even when the security level is set to 'secure' mode in the BIOS, i.e. the maximal effective security level for devices in the 'BootACL' is only 'user'. If 'BootACL' support is present, all new devices will be automatically added. Devices that are 'forgotten' (removed from 'boltd') will also be removed from the 'BootACL'. When a controller is offline, changes to the 'BootACL' will be written to a journal and synchronized back when the controller is online again. 'IOMMU' support: if the hardware and firmware support using the input–output memory management unit (IOMMU) to restrict direct memory access to certain safe regions, boltd will detect that feature and change its behavior: As long as iommu support is active, as indicated by the iommu_dma_protection sysfs attribute of the domain controller, new devices will be automatically enrolled with the 'iommu' policy and existing devices with 'iommu' (or 'auto') policy will be automatically authorized by boltd without any user interaction. When iommu is not active, devices that were enrolled with the 'iommu' policy will not be authorized automatically. The status of iommu support can be inspected by using *boltctl domains*. OPTIONS ------- *-h, --help*:: Prints a short help text and exits. *--version*:: Shows the version number and exits. *-r, --replace*:: Replace the currently running boltd instance. *--journal*:: Force logging to the journal. *-v, --verbose*:: Print debug output. ENVIRONMENT ----------- *`RUNTIME_DIRECTORY`*:: Specifies the path where the daemon stores data that only has to live as long as the current boot. Will be set automatically when started via systemd (>= 240). If not set the default path for runtime data is '/run/boltd'. *`STATE_DIRECTORY`*:: Specifies the path where the daemon stores device information, including the keys used for authorization. Overwrites the path that was set at compile time. Will be set automatically when started via systemd (>= 240). *`BOLT_DBPATH`*:: Same as `STATE_DIRECTORY` but takes precedence over that, if set. EXIT STATUS ----------- On success 0 is returned, a non-zero failure code otherwise. Author ------ Written by Christian Kellner . SEE ALSO -------- boltctl(1) bolt-0.9.2/meson.build000066400000000000000000000333311417453051000146270ustar00rootroot00000000000000project('bolt', 'c', version: '0.9.2', license : 'LGPL-2.1+', meson_version: '>= 0.46.0', default_options: ['warning_level=1', 'c_std=gnu99', 'buildtype=debugoptimized']) # additional compiler warnings, if supported test_args = [ '-fstack-protector-strong', '-Waggregate-return', '-Wunused', '-Warray-bounds', '-Wcast-align', '-Wclobbered', '-Wdeclaration-after-statement', '-Wempty-body', '-Wformat=2', '-Wformat-nonliteral', '-Wformat-security', '-Wformat-signedness', '-Wignored-qualifiers', '-Wimplicit-function-declaration', '-Winit-self', '-Wmissing-declarations', '-Wmissing-format-attribute', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wmissing-parameter-type', '-Wmissing-prototypes', '-Wnested-externs', '-Wno-discarded-qualifiers', '-Wno-missing-field-initializers', '-Wno-suggest-attribute=format', '-Wno-unused-parameter', '-Wold-style-definition', '-Woverride-init', '-Wpointer-arith', '-Wredundant-decls', '-Wreturn-type', '-Wshadow', '-Wsign-compare', '-Wstrict-aliasing=3', '-Wstrict-prototypes', '-Wstringop-overflow', '-Wstringop-truncation', '-Wtype-limits', '-Wundef', '-Wuninitialized', '-Wunused-but-set-variable', '-Wwrite-strings', ] compiler = meson.get_compiler('c') foreach arg: test_args if compiler.has_argument(arg) add_project_arguments(arg, language : 'c') endif endforeach # build options build_man = get_option('man') req_man = build_man == 'true' # dependencies gnome = import('gnome') # version requirements glib_min = [2, 56] glib_req = '>= @0@.@1@.0'.format(glib_min[0], glib_min[1]) # dependency objects glib = dependency('glib-2.0', version: glib_req) gio = dependency('gio-2.0') libudev = dependency('libudev') unix = dependency('gio-unix-2.0') udev = dependency('udev') polkit = dependency('polkit-gobject-1') mockdev = dependency('umockdev-1.0', required: false) git = find_program('git', required: false) a2x = find_program(['a2x', 'a2x.py'], required: req_man) # configuration & well known directories prefixdir = get_option('prefix') srcdir = meson.source_root() bindir = join_paths(prefixdir, get_option('bindir')) libexecdir = join_paths(prefixdir, get_option('libexecdir')) datadir = join_paths(prefixdir, get_option('datadir')) sysconfdir = join_paths(prefixdir, get_option('sysconfdir')) statedir = get_option('localstatedir') mandir = get_option('mandir') testsdir = join_paths(libexecdir, 'installed-tests', 'bolt') if get_option('db-path') != '' warning('db-path option is set, but will be ignored') endif dbname = get_option('db-name') dbdir = join_paths(statedir, 'lib', dbname) udevdir = udev.get_pkgconfig_variable('udevdir') unitdir = '' systemd = dependency('systemd', required: false) if systemd.found() unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') endif profiling = get_option('profiling') if profiling args = ['-pg'] profiling = compiler.has_multi_arguments(args) if profiling add_project_arguments(args, language: 'c') add_project_link_arguments(args, language: 'c') endif endif version_split = meson.project_version().split('.') version_major = version_split[0] version_minor = version_split[1] conf = configuration_data() conf.set_quoted('VERSION', meson.project_version()) conf.set_quoted('PACKAGE_NAME', meson.project_name()) conf.set_quoted('PACKAGE_VERSION', meson.project_version()) conf.set_quoted('DATADIR', datadir) conf.set_quoted('LOCALSTATEDIR', statedir) conf.set_quoted('BOLT_DBNAME', dbname) conf.set_quoted('BOLT_DBDIR', dbdir) conf.set('VERSION_MAJOR', version_major) conf.set('VERSION_MINOR', version_minor) conf.set('_GNU_SOURCE', true) foreach fn : [ ['copy_file_range', '''#include '''], ['explicit_bzero', '''#include '''], ['getrandom', '''#include '''], ] have = compiler.has_function(fn[0], prefix: fn[1], args: '-D_GNU_SOURCE') conf.set10('HAVE_FN_' + fn[0].to_upper(), have) endforeach if polkit.version().version_compare('>= 0.114') conf.set('HAVE_POLKIT_AUTOPTR', '1') endif conf.set('IS_COVERITY_BUILD', get_option('coverity')) glib_chk = 'GLIB_VERSION_@0@_@1@'.format(glib_min[0], glib_min[1]) conf.set('GLIB_VERSION_MIN_REQUIRED', glib_chk) conf.set('GLIB_VERSION_MAX_ALLOWED', glib_chk) config_h = configure_file( input: 'config.h.in', output: 'config.h', configuration: conf) cargs = ['-DG_LOG_DOMAIN="bolt"'] privileged_group = get_option('privileged-group') subs = configuration_data() subs.set('dbname', dbname) subs.set('dbdir', dbdir) subs.set('libexecdir', libexecdir) subs.set('privileged_group', privileged_group) subs.set('version', meson.project_version()) longdate = run_command('date', '+%a %b %d %Y').stdout().strip() subs.set('longdate', longdate) subs.set('reltag', 'pre1') if git.found() gitres = run_command(git, ['--git-dir=@0@/.git'.format(srcdir), 'rev-parse', '--short=7', 'HEAD']) if gitres.returncode() == 0 gitrev = gitres.stdout().strip() gitdate = run_command('date', '+%Y%m%d').stdout().strip() # YYYYMMDD subs.set('reltag', '@0@git@1@'.format(gitdate, gitrev)) endif endif # git hooks git_hooks = find_program(join_paths(srcdir, 'scripts', 'git-hooks.sh')) res = run_command(git_hooks, 'install') if res.returncode() != 0 lines = ['git hooks:'] + res.stdout().strip().split('\n') message('\n '.join(lines)) endif # common static library # contains code shared by daemon, command line tools common_deps = [glib, gio, libudev, unix] common_headers = [ 'common/bolt-enums.h', 'common/bolt-error.h' ] common_sources = [ 'common/bolt-dbus.c', 'common/bolt-enums.c', 'common/bolt-error.c', 'common/bolt-fs.c', 'common/bolt-glue.c', 'common/bolt-io.c', 'common/bolt-names.c', 'common/bolt-rnd.c', 'common/bolt-str.c', 'common/bolt-term.c', 'common/bolt-time.c', 'common/bolt-unix.c', 'common/bolt-wire.c', ] common_enums = gnome.mkenums_simple('bolt-enum-types', sources: common_headers) gen_sources = [common_enums[0]] gen_headers = [common_enums[1]] gen_sources += gnome.compile_resources( 'bolt-dbus-resource', 'data/dbus.gresource.xml', source_dir: 'data', c_name: 'bolt_dbus') common_lib = static_library('common', c_args : [cargs], sources: common_sources + gen_sources + gen_headers, dependencies: common_deps, include_directories: [ include_directories('common') ]) common = declare_dependency( sources: gen_headers, dependencies: common_deps, link_with: common_lib, include_directories: [ include_directories('common') ]) # boltd - the main daemon daemon_sources = files([ 'boltd/bolt-auth.c', 'boltd/bolt-bouncer.c', 'boltd/bolt-config.c', 'boltd/bolt-domain.c', 'boltd/bolt-exported.c', 'boltd/bolt-guard.c', 'boltd/bolt-journal.c', 'boltd/bolt-manager.c', 'boltd/bolt-power.c', 'boltd/bolt-device.c', 'boltd/bolt-key.c', 'boltd/bolt-log.c', 'boltd/bolt-reaper.c', 'boltd/bolt-store.c', 'boltd/bolt-sysfs.c', 'boltd/bolt-udev.c', 'boltd/bolt-watchdog.c' ]) install_data(['data/org.freedesktop.bolt.xml'], install_dir : join_paths(datadir, 'dbus-1', 'interfaces') ) install_data(['data/org.freedesktop.bolt.conf'], install_dir : join_paths(datadir, 'dbus-1', 'system.d') ) service_file = configure_file( input: 'data/org.freedesktop.bolt.service.in', output: 'org.freedesktop.bolt.service', configuration: subs ) install_data(service_file, install_dir: join_paths(datadir, 'dbus-1', 'system-services') ) policy_rules = configure_file( input: 'policy/org.freedesktop.bolt.rules.in', output: 'org.freedesktop.bolt.rules', configuration: subs ) install_data(policy_rules, install_dir: join_paths(datadir, 'polkit-1', 'rules.d') ) policy_file = configure_file( input: 'policy/org.freedesktop.bolt.policy.in', output: 'org.freedesktop.bolt.policy', configuration: subs ) install_data(policy_file, install_dir: join_paths(datadir, 'polkit-1', 'actions') ) unit_file = configure_file( input: 'data/bolt.service.in', output: 'bolt.service', configuration: subs ) if unitdir != '' install_data(unit_file, install_dir: unitdir ) endif install_data('data/90-bolt.rules', install_dir: join_paths(udevdir, 'rules.d') ) daemon_deps = [ glib, gio, libudev, polkit, unix, common ] daemon_library = static_library('daemon', c_args : [cargs], sources: daemon_sources, dependencies: daemon_deps, include_directories: [ include_directories('boltd') ]) libdaemon = declare_dependency( dependencies: daemon_deps, link_with: [daemon_library], include_directories: [ include_directories('boltd') ]) boltd = executable('boltd', ['boltd/bolt-daemon.c'], dependencies: [libdaemon], c_args : [ cargs, ], install: true, install_dir: libexecdir) # command line tools boltctl = executable('boltctl', ['cli/bolt-client.c', 'cli/bolt-device.c', 'cli/bolt-domain.c', 'cli/bolt-power.c', 'cli/bolt-proxy.c', 'cli/boltctl-authorize.c', 'cli/boltctl-config.c', 'cli/boltctl-domains.c', 'cli/boltctl-enroll.c', 'cli/boltctl-forget.c', 'cli/boltctl-info.c', 'cli/boltctl-list.c', 'cli/boltctl-monitor.c', 'cli/boltctl-power.c', 'cli/boltctl-uidfmt.c', 'cli/boltctl.c'], dependencies: [glib, gio, unix, common], c_args : [ cargs, ], install: true, install_dir: bindir) # testing install_tests = get_option('install-tests') test_resources = gnome.compile_resources( 'bolt-test-resources', 'tests/tests.gresource.xml', source_dir: 'tests', c_name: 'bolt_test') test_enums = gnome.mkenums_simple( 'test-enum-types', sources: ['tests/test-enums.h']) tests = [ ['test-auth', [libdaemon]], ['test-common', [], [test_resources, test_enums]], ['test-glue', [], test_enums], ['test-unix'], ['test-device', [libdaemon]], ['test-exported', [libdaemon], [test_resources, test_enums]], ['test-logging', [libdaemon]], ['test-store', [libdaemon]], ['test-journal', [libdaemon]], ['test-watchdog', [libdaemon]], ['test-self'], ['test-guard', [libdaemon]], ['test-reaper', [libdaemon]], ['test-wire', [libdaemon]], ] if mockdev.found() tests += [ ['test-power', [libdaemon, mockdev], ['tests/mock-sysfs.c']], ['test-sysfs', [libdaemon, mockdev], ['tests/mock-sysfs.c']], ['test-udev', [libdaemon, mockdev], ['tests/mock-sysfs.c']] ] endif foreach t: tests test_name = t.get(0) test_deps = [common] + t.get(1, []) test_srcs = ['tests/@0@.c'.format(test_name), 'tests/bolt-test.c', t.get(2, [])] test_exec = executable( test_name, test_srcs, dependencies: test_deps, include_directories: [ include_directories('tests') ], install: install_tests, install_dir: testsdir) test_env = environment() if test_deps.contains(mockdev) test_env.prepend('LD_PRELOAD', 'libumockdev-preload.so.0') endif test(test_name, test_exec, args: ['-m', get_option('tests-speed')], env: test_env, timeout: 120) endforeach test_it = find_program(join_paths(srcdir, 'tests', 'test-integration')) res = run_command(test_it, 'list-tests') if res.returncode() == 0 test_env = environment() test_env.prepend('LD_PRELOAD', 'libumockdev-preload.so.0') test_env.prepend('BOLT_BUILD_DIR', meson.current_build_dir()) tests = res.stdout().strip().split('\n') foreach t: tests args = t.split(' ') name = 'integration @0@'.format(args[1]) test(name, test_it, args: [args[0]], env: test_env, depends: [boltd, boltctl], timeout: 120) endforeach else msg = '@0@'.format(res.stderr().strip()) warning(msg) endif if install_tests install_data(test_it.path(), install_mode: 'rwxr-xr-x', install_dir: testsdir) install_data(join_paths(srcdir, 'contrib', 'bolt-mock'), install_mode: 'rwxr-xr-x', install_dir: testsdir) endif # contrib spec_file = configure_file( input: 'contrib/bolt.spec.in', output: 'bolt.spec', configuration: subs ) # documentation build_man = build_man != 'false' and a2x.found() if build_man foreach page : [ ['boltctl', '1'], ['boltd', '8'] ] name = page[0] section = page[1] custom_target( '@0@-man'.format(page[0]), build_by_default: true, input: 'docs/@0@.@1@.txt'.format(name, section), output: '@0@.@1@'.format(name, section), command: [ a2x, '-d', 'manpage', '-f', 'manpage', '-D', '@OUTDIR@', '-a', 'manmanual=bolt Manual', '-a', 'mansource=bolt', '-a', 'revnumber=@0@'.format(meson.project_version()), '@INPUT@' ], install: true, install_dir: join_paths(mandir, 'man@0@'.format(section)), ) endforeach endif meson.add_install_script('scripts/meson-install.sh', dbdir) run_target('uncrustify', command: 'scripts/uncrustify.sh') # all done, phew msg = ['', 'version: @0@'.format(meson.project_version()), 'sysconfdir: @0@'.format(sysconfdir), 'datadir: @0@'.format(datadir), 'statedir: @0@'.format(statedir), 'db name: @0@'.format(dbname), 'database path: @0@'.format(dbdir), 'privileged group: @0@'.format(privileged_group), '', 'udevdir: @0@'.format(udevdir), 'systemd unitdir: @0@'.format(unitdir), '', 'build manpage: @0@'.format(build_man), 'install tests: @0@'.format(install_tests), 'profiling (gprof): @0@'.format(profiling), 'tests speed: @0@'.format(get_option('tests-speed')), '' ] message('\n '.join(msg)) bolt-0.9.2/meson_options.txt000066400000000000000000000015311417453051000161170ustar00rootroot00000000000000option('coverity', type: 'boolean', value: 'false', description: 'Whether or not to do a coverity build') option('db-path', type: 'string', description: 'DEPRECATED') option('db-name', type: 'string', value: 'boltd', description: 'Name for the device database') option('install-tests', type: 'boolean', value: 'false', description: 'Install the tests') option('man', type: 'combo', choices: ['auto', 'true', 'false'], value: 'auto', description: 'Build man pages') option('privileged-group', type: 'string', value: 'wheel', description: 'Name of privileged group') option('profiling', type: 'boolean', value: 'false', description: 'Build with profiling support') option('systemd', type: 'boolean', value: 'true', description: 'DEPRECATED') option('tests-speed', type: 'combo', choices: ['quick', 'slow'], value: 'quick', description : 'Which tests to run') bolt-0.9.2/policy/000077500000000000000000000000001417453051000137615ustar00rootroot00000000000000bolt-0.9.2/policy/org.freedesktop.bolt.policy.in000066400000000000000000000034521417453051000216530ustar00rootroot00000000000000 Thunderbolt System services https://github.com/gicmo/bolt thunderbolt-symbolic Enroll new thunderbolt devices Authentication is required to enroll thunderbolt devices thunderbolt-symbolic auth_admin auth_admin auth_admin_keep Authorize thunderbolt devices Authentication is required to authorize thunderbolt devices thunderbolt-symbolic auth_admin auth_admin auth_admin_keep Manage thunderbolt devices Authentication is required to manage thunderbolt devices thunderbolt-symbolic auth_admin auth_admin auth_admin_keep bolt-0.9.2/policy/org.freedesktop.bolt.rules.in000066400000000000000000000005761417453051000215120ustar00rootroot00000000000000// -*- mode: js2 -*- polkit.addRule(function(action, subject) { if ((action.id === "org.freedesktop.bolt.enroll" || action.id === "org.freedesktop.bolt.authorize" || action.id === "org.freedesktop.bolt.manage") && subject.active === true && subject.local === true && subject.isInGroup("@privileged_group@")) { return polkit.Result.YES; } }); bolt-0.9.2/scripts/000077500000000000000000000000001417453051000141515ustar00rootroot00000000000000bolt-0.9.2/scripts/git-hooks.sh000077500000000000000000000030751417453051000164210ustar00rootroot00000000000000#!/bin/bash # # git hooks management # global setup set -u SRCDIR=${MESON_SOURCE_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. >/dev/null 2>&1 && pwd)} HOOKDIR="$SRCDIR/scripts/git-hooks/" DESTDIR="$SRCDIR/.git/hooks" cd "$SRCDIR" declare -a HOOKS=() readarray -t HOOKS < <(find "$HOOKDIR" -maxdepth 1 -type f -exec basename {} \;) # helpers ensure_destdir () { if [ ! -d "$DESTDIR" ]; then echo "$DESTDIR does not exist" exit 2 fi } # individual commands check () { ensure_destdir for hook in ${HOOKS[@]}; do echo -n "$hook " DEST="$DESTDIR/$hook" if [ -x "$DEST" ]; then echo "installed" else echo "missing" fi done } install () { ensure_destdir RESULT=0 for hook in ${HOOKS[@]}; do DEST="$DESTDIR/$hook" if [ -x "$DEST" ]; then continue fi cp -p "$HOOKDIR/$hook" "$DEST" chmod a+x "$DEST" ((RESULT -= 1)) echo "Installed '$hook' hook" done exit $RESULT } uninstall () { ensure_destdir for hook in ${HOOKS[@]}; do DEST="$DESTDIR/$hook" if [ ! -x "$DEST" ]; then continue fi rm "$DEST" echo "Uninstalled '$hook' hook" done } # main usage_error () { echo "usage: $0 " exit 1 } if [ "$#" -ne 1 ]; then usage_error fi case "$1" in check) check ;; install) install ;; uninstall) uninstall ;; *) usage_error ;; esac bolt-0.9.2/scripts/git-hooks/000077500000000000000000000000001417453051000160555ustar00rootroot00000000000000bolt-0.9.2/scripts/git-hooks/pre-commit000077500000000000000000000001061417453051000200540ustar00rootroot00000000000000#!/usr/bin/sh exec 1>&2 exec git diff-index --check --cached HEAD -- bolt-0.9.2/scripts/git-hooks/pre-push000077500000000000000000000012431417453051000175460ustar00rootroot00000000000000#!/bin/sh # Based on ".git/hooks/pre-push.sample" remote="$1" url="$2" # unused z40=0000000000000000000000000000000000000000 while read local_ref local_sha remote_ref remote_sha do if [ "$local_sha" = $z40 ]; then # ignore deletes continue fi if [ "$remote_sha" = $z40 ]; then # new branch, examine new commits starting $remote remote_sha=$(git rev-parse "$remote") fi range="$remote_sha..$local_sha" # check for fixup! commit commit=`git rev-list -n 1 --grep '^fixup! ' "$range"` if [ -n "$commit" ]; then echo >&2 "'fixup!' commit in $local_ref, not pushing" exit 1 fi done exit 0 bolt-0.9.2/scripts/meson-install.sh000066400000000000000000000003131417453051000172670ustar00rootroot00000000000000#!/bin/sh if [ -z $MESON_INSTALL_PREFIX ]; then echo 'This is meant to be run by meson' exit 1 fi BOLT_DBDIR=$1 echo "Creating database dir: ${BOLT_DBDIR}" mkdir -p "${DESTDIR}/${BOLT_DBDIR}" bolt-0.9.2/scripts/uncrustify.cfg000066400000000000000000000106561417453051000170550ustar00rootroot00000000000000newlines lf input_tab_size 8 output_tab_size 8 string_escape_char 92 string_escape_char2 0 # indenting indent_columns 2 indent_with_tabs 0 indent_align_string True indent_brace 2 indent_braces false indent_braces_no_func True indent_func_call_param false indent_func_def_param false indent_func_proto_param false indent_switch_case 0 indent_case_brace 2 indent_paren_close 1 # spacing sp_arith Add sp_assign Add sp_enum_assign Add sp_bool Add sp_compare Add sp_inside_paren Remove sp_inside_fparens Remove sp_func_def_paren Force sp_func_proto_paren Force sp_paren_paren Remove sp_balance_nested_parens False sp_paren_brace Remove sp_before_square Remove sp_before_squares Remove sp_inside_square Remove sp_before_ptr_star Add sp_between_ptr_star Remove sp_after_comma Add sp_before_comma Remove sp_after_cast Add sp_sizeof_paren Add sp_not Remove sp_inv Remove sp_addr Remove sp_member Remove sp_deref Remove sp_sign Remove sp_incdec Remove sp_attribute_paren remove sp_macro Force sp_func_call_paren Force sp_func_call_user_paren Remove set func_call_user _ N_ C_ g_autoptr g_auto sp_brace_typedef add sp_cond_colon add sp_cond_question add sp_defined_paren remove # alignment align_keep_tabs False align_with_tabs False align_on_tabstop False align_number_right False align_func_params True align_var_def_span 0 align_var_def_amp_style 1 align_var_def_colon true align_enum_equ_span 0 align_var_struct_span 2 align_var_def_star_style 2 align_var_def_amp_style 2 align_typedef_span 2 align_typedef_func 0 align_typedef_star_style 2 align_typedef_amp_style 2 # newlines nl_assign_leave_one_liners True nl_enum_leave_one_liners False nl_func_leave_one_liners False nl_if_leave_one_liners False nl_end_of_file Add nl_assign_brace Remove nl_func_var_def_blk 1 nl_fcall_brace Add nl_enum_brace Remove nl_struct_brace Force nl_union_brace Force nl_if_brace Force nl_brace_else Force nl_elseif_brace Force nl_else_brace Add nl_for_brace Force nl_while_brace Force nl_do_brace Force nl_brace_while Force nl_switch_brace Force nl_before_case True nl_after_case False nl_func_type_name Force nl_func_proto_type_name Remove nl_func_paren Remove nl_func_decl_start Remove nl_func_decl_args Force nl_func_decl_end Remove nl_fdef_brace Force nl_after_return False nl_define_macro False nl_create_if_one_liner False nl_create_for_one_liner False nl_create_while_one_liner False nl_after_semicolon True nl_multi_line_cond true # mod # I'd like these to be remove, but that removes brackets in if { if { foo } }, which i dislike # Not clear what to do about that... mod_full_brace_for Remove mod_full_brace_if Remove mod_full_brace_if_chain True mod_full_brace_while Remove mod_full_brace_do Remove mod_full_brace_nl 3 mod_paren_on_return Remove # line splitting #code_width = 78 ls_for_split_full True ls_func_split_full True # positioning pos_bool Trail pos_conditional Trail # custom keywords set FOR udev_list_entry_foreach bolt-0.9.2/scripts/uncrustify.sh000077500000000000000000000005701417453051000167250ustar00rootroot00000000000000#!/bin/bash SRCROOT=`git rev-parse --show-toplevel` CFG="$SRCROOT/scripts/uncrustify.cfg" echo "srcroot: $SRCROOT" case "$1" in -c|--check) OPTS="--check" ;; *) OPTS="--replace --no-backup" ;; esac pushd "$SRCROOT" uncrustify -c "$CFG" $OPTS `git ls-tree --name-only -r HEAD | grep \\\.[ch]$ | grep -v gvdb | grep -v build/` RES=$? popd exit $RES bolt-0.9.2/tests/000077500000000000000000000000001417453051000136245ustar00rootroot00000000000000bolt-0.9.2/tests/bolt-test.c000066400000000000000000000243341417453051000157130ustar00rootroot00000000000000/* * Copyright © 2018-2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-test.h" #include "bolt-fs.h" #include "bolt-io.h" #include "bolt-macros.h" #include "bolt-names.h" #include "bolt-str.h" #include #include #include #include #include #include #include #include BoltTmpDir bolt_tmp_dir_make (const char *pattern, GError **error) { g_autoptr(GError) err = NULL; BoltTmpDir dir = g_dir_make_tmp (pattern, &err); if (dir == NULL) { if (error) bolt_error_propagate (error, &err); else g_critical ("could not create tmp dir [%s]: %s", pattern, err->message); return NULL; } g_debug ("tmp dir made at '%s'", dir); return dir; } void bolt_tmp_dir_destroy (BoltTmpDir dir) { g_autoptr(GError) err = NULL; gboolean ok; if (dir == NULL) return; g_debug ("cleaning tmp dir at '%s'", dir); ok = bolt_fs_cleanup_dir (dir, &err); if (!ok) g_warning ("could not clean up dir: %s", err->message); g_free (dir); } /* Notification Socket */ struct NotifySocket { BoltTmpDir tmpdir; char *socket_path; guint socket_watch; int socket_fd; /* */ guint counter; GQueue messages; }; union ctrlmsg { struct cmsghdr hdr; guint8 buf[CMSG_SPACE (sizeof (struct ucred))]; }; NotifySocket * notify_socket_new (void) { g_autoptr(GError) err = NULL; bolt_autoclose int fd = -1; NotifySocket *ns = NULL; static const int one = 1; struct sockaddr_un sau = {AF_UNIX, {'\0', }}; size_t socklen; int r; ns = g_new0 (NotifySocket, 1); ns->tmpdir = bolt_tmp_dir_make ("bolt.unix.XXXXXX", &err); g_assert_no_error (err); g_assert_nonnull (ns->tmpdir); fd = socket (AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); assert (fd > -1); /* plain assert for coverity */ ns->socket_path = g_build_filename (ns->tmpdir, "notify_socket", NULL); strncpy (sau.sun_path, ns->socket_path, sizeof (sau.sun_path) - 1); socklen = offsetof (struct sockaddr_un, sun_path) + strlen (sau.sun_path) + 1; r = bind (fd, &sau, socklen); g_assert_cmpint (r, >, -1); r = setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof (one)); g_assert_cmpint (r, >, -1); g_queue_init (&ns->messages); ns->socket_fd = bolt_steal (&fd, -1); g_debug ("notification socket at '%s'", sau.sun_path); return ns; } void notify_socket_free (NotifySocket *ns) { g_autoptr(GError) err = NULL; g_clear_handle_id (&ns->socket_watch, g_source_remove); if (ns->socket_fd > -1) { bolt_close (ns->socket_fd, &err); g_assert_no_error (err); ns->socket_fd = -1; } g_clear_pointer (&ns->tmpdir, bolt_tmp_dir_destroy); g_queue_free_full (&ns->messages, g_free); g_clear_pointer (&ns->socket_path, g_free); g_free (ns); } char * notify_socket_revmsg (NotifySocket *ns, gboolean queue) { char data[4096]; char *msg; struct iovec iov = { .iov_base = data, .iov_len = sizeof (data) - 1, }; union ctrlmsg crtl = {}; struct msghdr hdr = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = &crtl, .msg_controllen = sizeof (crtl), }; struct ucred *ucred = NULL; ssize_t r; /* MSG_TRUNC: return the real size */ r = recvmsg (ns->socket_fd, &hdr, MSG_DONTWAIT | MSG_CMSG_CLOEXEC | MSG_TRUNC); if (r < 0) { if (errno == EINTR || errno == EAGAIN) return NULL; g_critical ("i/o error reading from notify socket: %m"); return NULL; } if (hdr.msg_flags & MSG_TRUNC || ((size_t) r > sizeof (data) - 1)) { g_warning ("notification message truncated"); return NULL; } g_assert_cmpint (r, <, sizeof (data)); data[r] = '\0'; ns->counter++; msg = g_strdup (data); for (struct cmsghdr *c = CMSG_FIRSTHDR (&hdr); c != NULL; c = CMSG_NXTHDR (&hdr, c)) { if (c->cmsg_level != SOL_SOCKET) continue; if (c->cmsg_type == SCM_CREDENTIALS && c->cmsg_len == CMSG_LEN (sizeof (struct ucred))) ucred = (struct ucred *) (void *) CMSG_DATA (c); } if (queue) g_queue_push_tail (&ns->messages, msg); g_debug ("got message: '%s' [%s]", msg, bolt_yesno (queue)); if (ucred != NULL) g_debug (" ucred, pid: %i, uid: %li, gid: %li", (int) ucred->pid, (long) ucred->uid, (long) ucred->gid); return msg; } static gboolean got_notification (gpointer user_data) { NotifySocket *ns = (NotifySocket *) user_data; notify_socket_revmsg (ns, TRUE); return TRUE; } void notify_socket_enable_watch (NotifySocket *ns) { g_autoptr(GSource) source = NULL; g_assert_nonnull (ns); g_assert_cmpuint (ns->socket_fd, >, -1); source = g_unix_fd_source_new (ns->socket_fd, G_IO_IN); g_assert_nonnull (source); g_source_set_callback (source, got_notification, ns, NULL); ns->socket_watch = g_source_attach (source, NULL); } void notify_socket_set_environment (NotifySocket *ns) { g_setenv (BOLT_SD_NOTIFY_SOCKET, ns->socket_path, TRUE); } void notify_socket_make_pollfd (NotifySocket *ns, GPollFD *fd) { memset (fd, 0, sizeof (GPollFD)); fd->fd = ns->socket_fd; fd->events = G_IO_IN | G_IO_HUP | G_IO_ERR; } /* Version parsing, checking */ static gboolean parse_one (const char *str, BoltVersion *version, int *index, GError **error) { gboolean ok; gint64 v; int i = *index; ok = g_ascii_string_to_signed (str, 10, /* base */ 0, G_MAXINT, &v, error); if (!ok) return FALSE; version->triplet[i] = (int) v; *index = i + 1; return TRUE; } gboolean bolt_version_parse (const char *str, BoltVersion *version, GError **error) { g_autofree char *tmp = NULL; gboolean ok = FALSE; char *data = NULL; char *delim = NULL; int index = 0; g_return_val_if_fail (str != NULL, FALSE); g_return_val_if_fail (version != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); bolt_version_clear (version); /* so we manipulate the string */ tmp = data = g_strdup (str); (void) tmp; /* 'dead' store for g_autofree */ delim = strchr (data, '-'); if (delim) { *delim = '\0'; version->suffix = g_strdup (delim + 1); } while (index < 2 && (delim = strchr (data, '.')) != NULL) { *delim = '\0'; ok = parse_one (data, version, &index, error); if (!ok) return FALSE; data = delim + 1; } return parse_one (data, version, &index, error); } void bolt_version_clear (BoltVersion *version) { version->major = -1; version->minor = -1; version->patch = -1; g_clear_pointer (&version->suffix, g_free); } /* strcmp semantics */ int bolt_version_compare (BoltVersion *a, BoltVersion *b) { g_return_val_if_fail (a != NULL, -1); g_return_val_if_fail (b != NULL, 1); for (int i = 0; i < 3; i++) { int ac = a->triplet[i]; int bc = b->triplet[i]; if (ac < bc) return -1; else if (ac > bc) return 1; } return 0; /* all components equal */ } gboolean bolt_version_check (BoltVersion *base, int major, int minor, int patch) { BoltVersion ref = BOLT_VERSION_INIT (major, minor, patch); g_return_val_if_fail (base != NULL, FALSE); return bolt_version_compare (base, &ref) >= 0; } gboolean bolt_check_kernel_version (int major, int minor) { g_autoptr(GError) err = NULL; g_auto(BoltVersion) ver = BOLT_VERSION_INIT (1, 0, 0); g_autofree char *data = NULL; gboolean ok; gsize length; ok = g_file_get_contents ("/proc/sys/kernel/osrelease", &data, &length, &err); if (!ok) { g_message ("Could not read kernel version: %s", err->message); return FALSE; } while (length > 1 && data[length - 1] == '\n') data[--length] = '\0'; ok = bolt_version_parse (data, &ver, &err); if (!ok) { g_message ("Could not parse kernel version (%s): %s", data, err->message); return FALSE; } g_debug ("Read kernel version: %d.%d.%d (%s)", ver.major, ver.minor, ver.patch, (ver.suffix ? : "")); return bolt_version_check (&ver, major, minor, -1); } typedef struct MainLoopCtx { GMainLoop *loop; gboolean timeout; } MainLoopCtx; static gboolean on_main_loop_timeout (gpointer user_data) { MainLoopCtx *ctx = user_data; ctx->timeout = TRUE; g_main_loop_quit (ctx->loop); return G_SOURCE_REMOVE; } gboolean bolt_test_run_main_loop (GMainLoop *loop, guint timeout_seconds, gboolean exit_on_timeout, GError **error) { MainLoopCtx ctx = { .loop = loop, .timeout = FALSE }; guint tid; tid = g_timeout_add_seconds (timeout_seconds, on_main_loop_timeout, &ctx); if (ctx.timeout) { const char *message = "Operation timed out"; g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "%s", message); if (exit_on_timeout) { g_warning ("test error: %s", message); g_assert_not_reached (); } return FALSE; } g_source_remove (tid); return TRUE; } bolt-0.9.2/tests/bolt-test.h000066400000000000000000000122321417453051000157120ustar00rootroot00000000000000/* * Copyright © 2018-2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include G_BEGIN_DECLS typedef char *BoltTmpDir; BoltTmpDir bolt_tmp_dir_make (const char *pattern, GError **error); void bolt_tmp_dir_destroy (BoltTmpDir dir); G_DEFINE_AUTO_CLEANUP_FREE_FUNC (BoltTmpDir, bolt_tmp_dir_destroy, NULL) /* helper macro */ /* *INDENT-OFF* */ #define bolt_assert_strv_equal(a, b, n) G_STMT_START { \ const GStrv sa__ = (a); \ const GStrv sb__ = (b); \ guint al__ = a != NULL ? g_strv_length (sa__) : 0; \ guint bl__ = b != NULL ? g_strv_length (sb__) : 0; \ if (n > 0) { \ al__ = MIN ((guint) n, al__); \ bl__ = MIN ((guint) n, bl__); \ } \ if (al__ != bl__) \ g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ "len(" #a ") == len(" #b ")", \ (long double) al__, "!=", (long double) bl__, 'i'); \ else \ for (guint il__ = 0; il__ < al__; il__++) \ if (g_strcmp0 (sa__[il__], sb__[il__]) == 0) {; } else { \ g_autofree char *va__ = NULL; \ va__ = g_strdup_printf (#a "[%u] != " #b "[%u]", il__, il__ ); \ g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ va__, sa__[il__], "!=", sb__[il__]); \ } \ } G_STMT_END #define skip_test_if(condition, message) G_STMT_START { \ if (condition) \ { \ g_test_skip (message); \ return; \ } \ } G_STMT_END #define skip_test_unless(condition, message) skip_test_if(!(condition), message) /* *INDENT-ON* */ /* Notification Socket */ typedef struct NotifySocket NotifySocket; NotifySocket * notify_socket_new (void); void notify_socket_free (NotifySocket *ns); char * notify_socket_revmsg (NotifySocket *ns, gboolean queue); void notify_socket_enable_watch (NotifySocket *ns); void notify_socket_set_environment (NotifySocket *ns); void notify_socket_make_pollfd (NotifySocket *ns, GPollFD *fd); /* Version parsing, checking */ typedef struct BoltVersion_ BoltVersion; struct BoltVersion_ { union { struct { int major; int minor; int patch; }; int triplet[3]; }; char *suffix; }; #define BOLT_VERSION_INIT(ma, mi, pa) {.major = (ma), .minor = (mi), .patch = (pa), .suffix = NULL} gboolean bolt_version_parse (const char *str, BoltVersion *version, GError **error); void bolt_version_clear (BoltVersion *version); int bolt_version_compare (BoltVersion *a, BoltVersion *b); gboolean bolt_version_check (BoltVersion *version, int major, int minor, int patch); G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (BoltVersion, bolt_version_clear); gboolean bolt_check_kernel_version (int major, int minor); gboolean bolt_test_run_main_loop (GMainLoop *loop, guint timeout_seconds, gboolean exit_on_timeout, GError **error); G_END_DECLS bolt-0.9.2/tests/example.bolt.xml000066400000000000000000000015531417453051000167440ustar00rootroot00000000000000 bolt-0.9.2/tests/mock-sysfs.c000066400000000000000000000645311417453051000160770ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-io.h" #include "bolt-fs.h" #include "bolt-names.h" #include "bolt-str.h" #include "mock-sysfs.h" #include #include #include #include #include #include typedef struct _MockDevice MockDevice; struct _MockDevice { char *idstr; char *path; gint serial; /* */ GHashTable *devices; }; static void mock_device_destroy (gpointer data) { MockDevice *device = data; if (device == NULL) return; g_clear_pointer (&device->devices, g_hash_table_unref); g_free (device->path); g_free (device->idstr); g_free (device); } typedef struct _MockDomain MockDomain; struct _MockDomain { guint id; char *idstr; char *path; gint serial; guint32 nhi_id; char *nhi_idstr; char *nhi_path; MockDevice *host; }; static void mock_domain_destory (gpointer data) { MockDomain *domain = data; mock_device_destroy (domain->host); g_free (domain->nhi_idstr); g_free (domain->nhi_path); g_free (domain->idstr); g_free (domain->path); g_free (domain); } /* prototypes */ static MockDevice * mock_sysfs_device_plug (MockSysfs *ms, MockDomain *domain, char *parent, MockDevId *id, guint authorized, const char *key, gint boot, BoltLinkSpeed *link); static void mock_sysfs_device_unplug (MockSysfs *ms, MockDevice *dev); struct _MockSysfs { GObject object; /* umockdev */ UMockdevTestbed *bed; /* state tracking */ char *force_power; GHashTable *domains; GHashTable *devices; char *dmi; }; enum { PROP_0, PROP_TESTBED, PROP_LAST }; static GParamSpec *sysfs_props[PROP_LAST] = { NULL, }; G_DEFINE_TYPE (MockSysfs, mock_sysfs, G_TYPE_OBJECT); static void mock_sysfs_finalize (GObject *object) { MockSysfs *ms = MOCK_SYSFS (object); if (ms->dmi) mock_sysfs_dmi_id_remove (ms); if (ms->force_power) mock_sysfs_force_power_remove (ms); g_clear_pointer (&ms->domains, g_hash_table_unref); g_clear_pointer (&ms->devices, g_hash_table_unref); g_clear_object (&ms->bed); G_OBJECT_CLASS (mock_sysfs_parent_class)->finalize (object); } static void mock_sysfs_init (MockSysfs *ms) { g_autofree char *bus = NULL; g_autofree char *cls = NULL; g_autofree char *sys = NULL; int r; ms->bed = umockdev_testbed_new (); ms->domains = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, mock_domain_destory); ms->devices = g_hash_table_new (g_str_hash, g_str_equal); /* udev_enumerate_scan_devices() will return -ENOENT, if * sys/bus or sys/class directories can not be found; * fixed in https://github.com/martinpitt/umockdev/commit/5e8296014346 * but work with older versions as well */ sys = umockdev_testbed_get_sys_dir (ms->bed); bus = g_build_filename (sys, "bus", NULL); r = g_mkdir (bus, 0744); if (r < 0 && errno != EEXIST) g_warning ("could not create %s", bus); cls = g_build_filename (sys, "class", NULL); r = g_mkdir (cls, 0744); if (r < 0 && errno != EEXIST) g_warning ("could not create %s", bus); } static void mock_sysfs_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MockSysfs *ms = MOCK_SYSFS (object); switch (prop_id) { case PROP_TESTBED: g_value_set_object (value, ms->bed); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void mock_sysfs_class_init (MockSysfsClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = mock_sysfs_finalize; gobject_class->get_property = mock_sysfs_get_property; sysfs_props[PROP_TESTBED] = g_param_spec_object ("testbed", NULL, NULL, UMOCKDEV_TYPE_TESTBED, G_PARAM_READABLE | G_PARAM_STATIC_NICK); g_object_class_install_properties (gobject_class, PROP_LAST, sysfs_props); } /* internal */ #define CONST_STRV(...) (char **) (const char *[]){ __VA_ARGS__} static MockDevice * mock_sysfs_device_plug (MockSysfs *ms, MockDomain *domain, char *parent, MockDevId *id, guint authorized, const char *key, gint boot, BoltLinkSpeed *link) { g_autofree char *idstr = NULL; g_autofree char *vendor_id = NULL; g_autofree char *device_id = NULL; g_autofree char *authstr = NULL; g_autofree char *bootstr = NULL; g_autofree char *rx_speed = NULL; g_autofree char *tx_speed = NULL; g_autofree char *rx_lanes = NULL; g_autofree char *tx_lanes = NULL; const char *props[25] = {NULL, }; MockDevice *device; guint serial; char *path; guint i; serial = (domain->serial)++; idstr = g_strdup_printf ("%u-%u", domain->id, serial); authstr = g_strdup_printf ("%u", authorized); i = 0; if (id->vendor_id) { vendor_id = g_strdup_printf ("%d", id->vendor_id); props[i++] = "vendor"; props[i++] = vendor_id; } if (id->vendor_name) { props[i++] = "vendor_name"; props[i++] = id->vendor_name; } if (id->device_id) { device_id = g_strdup_printf ("%d", id->device_id); props[i++] = "device"; props[i++] = device_id; } if (id->device_name) { props[i++] = "device_name"; props[i++] = id->device_name; } if (id->unique_id) { props[i++] = "unique_id"; props[i++] = id->unique_id; } props[i++] = "authorized"; props[i++] = authstr; if (key != NULL) { props[i++] = "key"; props[i++] = key; } if (boot > 0) { bootstr = g_strdup_printf ("%d", boot); props[i++] = "boot"; props[i++] = bootstr; } if (link) { rx_speed = g_strdup_printf ("%u Gb/s\n", link->rx.speed); tx_speed = g_strdup_printf ("%u Gb/s\n", link->tx.speed); rx_lanes = g_strdup_printf ("%u\n", link->rx.lanes); tx_lanes = g_strdup_printf ("%u\n", link->tx.lanes); props[i++] = "rx_speed"; props[i++] = rx_speed; props[i++] = "tx_speed"; props[i++] = tx_speed; props[i++] = "rx_lanes"; props[i++] = rx_lanes; props[i++] = "tx_lanes"; props[i++] = tx_lanes; } props[i++] = NULL; g_assert (sizeof (props) >= i); path = umockdev_testbed_add_devicev (ms->bed, "thunderbolt", idstr, parent, (char **) props, CONST_STRV ("DEVTYPE", "thunderbolt_device", NULL)); if (path == NULL) return NULL; g_debug ("M [A] %s (%s) @ %s", idstr, authstr, path); device = g_new0 (MockDevice, 1); device->idstr = g_steal_pointer (&idstr); device->path = path; device->devices = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, mock_device_destroy); g_hash_table_replace (ms->devices, device->idstr, device); return device; } static void mock_sysfs_device_unplug (MockSysfs *ms, MockDevice *dev) { GHashTableIter iter; gpointer k, v; g_hash_table_iter_init (&iter, dev->devices); while (g_hash_table_iter_next (&iter, &k, &v)) { MockDevice *child = v; mock_sysfs_device_unplug (ms, child); g_hash_table_iter_remove (&iter); } g_clear_pointer (&dev->devices, g_hash_table_unref); g_debug ("M [R] %s @ %s", dev->idstr, dev->path); umockdev_testbed_uevent (ms->bed, dev->path, "remove"); umockdev_testbed_remove_device (ms->bed, dev->path); } /* public methods: generic */ MockSysfs * mock_sysfs_new (void) { MockSysfs *ms; ms = g_object_new (MOCK_TYPE_SYSFS, NULL); return ms; } /* public methods: force-power */ const char * mock_sysfs_force_power_add (MockSysfs *ms) { char *path; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (ms->force_power == NULL, NULL); path = umockdev_testbed_add_device (ms->bed, "wmi", INTEL_WMI_THUNDERBOLT_GUID, NULL, "force_power", "", NULL, "WMI_GUID", INTEL_WMI_THUNDERBOLT_GUID, "DRIVER", "intel-wmi-thunderbolt", NULL); ms->force_power = path; return path; } gboolean mock_sysfs_force_power_remove (MockSysfs *ms) { g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); g_return_val_if_fail (ms->force_power != NULL, FALSE); umockdev_testbed_uevent (ms->bed, ms->force_power, "remove"); umockdev_testbed_remove_device (ms->bed, ms->force_power); g_clear_pointer (&ms->force_power, g_free); return TRUE; } void mock_sysfs_force_power_load (MockSysfs *ms) { g_return_if_fail (MOCK_IS_SYSFS (ms)); g_return_if_fail (ms->force_power != NULL); umockdev_testbed_set_attribute (ms->bed, ms->force_power, "force_power", ""); umockdev_testbed_set_property (ms->bed, ms->force_power, "DRIVER", "intel-wmi-thunderbolt"); umockdev_testbed_uevent (ms->bed, ms->force_power, "change"); umockdev_testbed_uevent (ms->bed, ms->force_power, "bind"); } void mock_sysfs_force_power_unload (MockSysfs *ms) { g_autofree char *root = NULL; g_autofree char *path = NULL; int r; g_return_if_fail (MOCK_IS_SYSFS (ms)); g_return_if_fail (ms->force_power != NULL); root = umockdev_testbed_get_root_dir (ms->bed); path = g_build_filename (root, ms->force_power, "force_power", NULL); r = g_unlink (path); if (r == -1) g_warning ("could not unlink %s: %s", path, g_strerror (errno)); umockdev_testbed_set_property (ms->bed, ms->force_power, "DRIVER", ""); umockdev_testbed_uevent (ms->bed, ms->force_power, "change"); umockdev_testbed_uevent (ms->bed, ms->force_power, "unbind"); } char * mock_sysfs_force_power_read (MockSysfs *ms) { g_autoptr(GError) err = NULL; g_autofree char *path = NULL; char *data; gboolean ok; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (ms->force_power != NULL, NULL); path = g_build_filename (ms->force_power, "force_power", NULL); ok = g_file_get_contents (path, &data, NULL, &err); if (!ok) { g_warning ("could not read force power file: %s", err->message); return NULL; } return data; } gboolean mock_sysfs_force_power_enabled (MockSysfs *ms) { g_autofree char *data = NULL; data = mock_sysfs_force_power_read (ms); data = g_strstrip (data); return bolt_streq (data, "1"); } /* dmi */ const char * mock_sysfs_dmi_id_add (MockSysfs *ms, const char *sys_vendor, const char *product_name, const char *product_version) { const char *props[25] = {NULL, }; guint i = 0; char *path; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (ms->dmi == NULL, NULL); props[i++] = BOLT_SYSFS_DMI_SYS_VENDOR; props[i++] = sys_vendor; props[i++] = BOLT_SYSFS_DMI_PRODUCT_NAME; props[i++] = product_name; props[i++] = BOLT_SYSFS_DMI_PRODUCT_VERSION; props[i++] = product_version; props[i++] = NULL; g_assert (sizeof (props) >= i); path = umockdev_testbed_add_devicev (ms->bed, "dmi", "id", NULL, (char **) props, NULL); ms->dmi = path; return path; } gboolean mock_sysfs_dmi_id_remove (MockSysfs *ms) { g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); g_return_val_if_fail (ms->dmi != NULL, FALSE); umockdev_testbed_uevent (ms->bed, ms->dmi, "remove"); umockdev_testbed_remove_device (ms->bed, ms->dmi); g_clear_pointer (&ms->dmi, g_free); return TRUE; } /* public methods: domain */ const char * mock_sysfs_domain_add (MockSysfs *ms, BoltSecurity security, ...) { g_autofree char *acl = NULL; g_autofree char *nhi_pciid = NULL; g_autofree char *nhi_idstr = NULL; g_autofree char *nhi_path = NULL; const char *props[7] = {NULL, }; const char *secstr; const char *key; MockDomain *domain; va_list args; guint32 nhi = 0x15d2; char *idstr = NULL; char *path; guint id; guint i; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); id = g_hash_table_size (ms->domains); secstr = bolt_security_to_string (security); idstr = g_strdup_printf ("domain%u", id); i = 0; props[i++] = "security"; props[i++] = secstr; va_start (args, security); while ((key = va_arg (args, const char *)) != NULL) { if (bolt_streq (key, "bootacl")) { char ** bootacl = va_arg (args, char **); acl = g_strjoinv (",", bootacl); props[i++] = "boot_acl"; props[i++] = acl; } else if (bolt_streq (key, "iommu")) { const char *iommu = va_arg (args, const char *); props[i++] = BOLT_SYSFS_IOMMU; props[i++] = iommu; } else if (bolt_streq (key, "nhi")) { nhi = va_arg (args, int); } } g_assert (i < 7); va_end (args); props[i++] = NULL; g_assert (sizeof (props) >= i); /* native host interface (NHI) */ nhi_pciid = g_strdup_printf ("0x%04x", nhi); nhi_idstr = g_strdup_printf ("0000:00:01.%u", id); nhi_path = umockdev_testbed_add_devicev (ms->bed, "pci", nhi_idstr, NULL, /* parent: NHI has none */ CONST_STRV ("class", "0x088000", "vendor", "0x8086", /* Intel */ "device", nhi_pciid, NULL), CONST_STRV ("DRIVER", "thunderbolt", NULL)); g_debug ("M [A] %s (0x%04x) @ %s", nhi_idstr, nhi, nhi_path); /* add the domain */ path = umockdev_testbed_add_devicev (ms->bed, "thunderbolt", idstr, nhi_path, /* parent */ (char **) props, CONST_STRV ("DEVTYPE", "thunderbolt_domain", NULL)); if (path == NULL) { umockdev_testbed_remove_device (ms->bed, nhi_path); return path; } g_debug ("M [A] %s (%s) @ %s", idstr, secstr, path); domain = g_new0 (MockDomain, 1); domain->nhi_id = nhi; domain->nhi_idstr = g_steal_pointer (&nhi_idstr); domain->nhi_path = g_steal_pointer (&nhi_path); domain->id = id; domain->idstr = idstr; domain->path = path; g_hash_table_insert (ms->domains, idstr, domain); return idstr; } const char * mock_sysfs_domain_get_syspath (MockSysfs *ms, const char *id) { MockDomain *domain; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (id != NULL, NULL); domain = g_hash_table_lookup (ms->domains, id); if (domain == NULL) return NULL; return domain->path; } gboolean mock_sysfs_domain_remove (MockSysfs *ms, const char *id) { MockDomain *domain; g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); g_return_val_if_fail (id != NULL, FALSE); domain = g_hash_table_lookup (ms->domains, id); if (domain == NULL) return FALSE; if (domain->host) { mock_sysfs_device_unplug (ms, domain->host); g_clear_pointer (&domain->host, mock_device_destroy); } g_debug ("M [R] %s @ %s", domain->idstr, domain->path); umockdev_testbed_uevent (ms->bed, domain->path, "remove"); umockdev_testbed_remove_device (ms->bed, domain->path); g_debug ("M [R] %s @ %s", domain->nhi_idstr, domain->nhi_path); umockdev_testbed_uevent (ms->bed, domain->nhi_path, "remove"); umockdev_testbed_remove_device (ms->bed, domain->nhi_path); g_hash_table_remove (ms->domains, id); return TRUE; } GStrv mock_sysfs_domain_bootacl_get (MockSysfs *ms, const char *id, GError **error) { g_autofree char *path = NULL; g_autofree char *data = NULL; GStrv acl; MockDomain *domain; gboolean ok; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (id != NULL, NULL); domain = g_hash_table_lookup (ms->domains, id); if (domain == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "domain '%s' not found", id); return NULL; } path = g_build_filename (domain->path, "boot_acl", NULL); ok = g_file_get_contents (path, &data, NULL, error); if (!ok) return NULL; acl = g_strsplit (data, ",", -1); return acl; } gboolean mock_sysfs_domain_bootacl_set (MockSysfs *ms, const char *id, GStrv acl, GError **error) { g_autofree char *path = NULL; g_autofree char *data = NULL; MockDomain *domain; gboolean ok; g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); g_return_val_if_fail (id != NULL, FALSE); g_return_val_if_fail (acl != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); domain = g_hash_table_lookup (ms->domains, id); if (domain == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "domain '%s' not found", id); return FALSE; } data = g_strjoinv (",", acl); path = g_build_filename (domain->path, "boot_acl", NULL); ok = bolt_file_write_all (path, data, -1, error); if (!ok) return FALSE; umockdev_testbed_uevent (ms->bed, domain->path, "change"); return TRUE; } gboolean mock_syfs_domain_iommu_set (MockSysfs *ms, const char *id, const char *val, GError **error) { g_autofree char *path = NULL; MockDomain *domain; gboolean ok; g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); g_return_val_if_fail (id != NULL, FALSE); g_return_val_if_fail (val != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); domain = g_hash_table_lookup (ms->domains, id); if (domain == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "domain '%s' not found", id); return FALSE; } path = g_build_filename (domain->path, BOLT_SYSFS_IOMMU, NULL); ok = bolt_file_write_all (path, val, -1, error); if (!ok) return FALSE; umockdev_testbed_uevent (ms->bed, domain->path, "change"); return TRUE; } const char * mock_sysfs_host_add (MockSysfs *ms, const char *dom, MockDevId *id) { MockDomain *domain; MockDevice *device; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (dom != NULL, NULL); domain = g_hash_table_lookup (ms->domains, dom); if (domain == NULL) { g_warning ("domain '%s' not found", dom); return NULL; } if (domain->host != NULL) { g_warning ("domain '%s' already has a host", dom); return NULL; } device = mock_sysfs_device_plug (ms, domain, domain->path, id, 1, NULL, /* no key for the host */ -1, /* no boot file either */ NULL); /* no link settings */ domain->host = device; return device->idstr; } void mock_sysfs_host_remove (MockSysfs *ms, const char *host) { GHashTableIter iter; gpointer k, v; MockDevice *dev; MockDomain *domain = NULL; g_return_if_fail (MOCK_IS_SYSFS (ms)); g_return_if_fail (host != NULL); dev = g_hash_table_lookup (ms->devices, host); if (dev == NULL) { g_error ("Device not found for %s", host); return; } g_hash_table_iter_init (&iter, ms->domains); while (g_hash_table_iter_next (&iter, &k, &v)) { MockDomain *d = v; if (d->host == dev) { domain = d; break; } } if (!domain) { g_error ("domain not found for host: %s", host); return; } mock_sysfs_device_unplug (ms, dev); mock_device_destroy (dev); domain->host = NULL; } const char * mock_sysfs_device_add (MockSysfs *ms, const char *parent, MockDevId *id, guint authorized, const char *key, gint boot, BoltLinkSpeed *speed) { MockDevice *pdev; MockDomain *domain = NULL; MockDevice *device; GHashTableIter iter; gpointer k, v; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (parent != NULL, NULL); pdev = g_hash_table_lookup (ms->devices, parent); if (pdev == NULL) { g_warning ("parent device '%s' not found", parent); return NULL; } /* look up the domain for the device */ g_hash_table_iter_init (&iter, ms->domains); while (g_hash_table_iter_next (&iter, &k, &v)) { MockDomain *d = v; if (g_str_has_prefix (pdev->path, d->path)) { domain = d; break; } } if (domain == NULL) { g_warning ("domain not found for device '%s'", parent); return NULL; } device = mock_sysfs_device_plug (ms, domain, pdev->path, id, 1, key, boot, speed); g_hash_table_insert (pdev->devices, device->idstr, device); return device->idstr; } const char * mock_sysfs_device_get_syspath (MockSysfs *ms, const char *id) { MockDevice *dev; g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); g_return_val_if_fail (id != NULL, NULL); dev = g_hash_table_lookup (ms->devices, id); if (dev == NULL) return NULL; return dev->path; } const char * mock_sysfs_device_get_parent (MockSysfs *ms, const char *id) { MockDevice *dev; GHashTableIter iter; gpointer k, v; g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); g_return_val_if_fail (id != NULL, FALSE); dev = g_hash_table_lookup (ms->devices, id); if (dev == NULL) return FALSE; /* we look for the device that contains 'dev' * in its devices table, because that will be * the parent */ g_hash_table_iter_init (&iter, ms->devices); while (g_hash_table_iter_next (&iter, &k, &v)) { MockDevice *d = v; gpointer p; p = g_hash_table_lookup (d->devices, dev->path); if (p != NULL) return d->idstr; } return NULL; } gboolean mock_sysfs_device_remove (MockSysfs *ms, const char *id) { const char *mom; MockDevice *m; MockDevice *dev; g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); g_return_val_if_fail (id != NULL, FALSE); dev = g_hash_table_lookup (ms->devices, id); if (dev == NULL) return FALSE; mom = mock_sysfs_device_get_parent (ms, id); if (mom == NULL) return FALSE; m = g_hash_table_lookup (ms->devices, mom); g_assert_nonnull (m); mock_sysfs_device_unplug (ms, dev); g_hash_table_remove (m->devices, id); mock_device_destroy (dev); return TRUE; } gboolean mock_sysfs_set_osrelease (MockSysfs *ms, const char *version) { g_autoptr(GError) err = NULL; g_autoptr(GFile) target = NULL; g_autofree char *data = NULL; g_autofree char *path = NULL; g_autofree char *root = NULL; gboolean ok; gsize n; int r; root = umockdev_testbed_get_root_dir (ms->bed); target = g_file_new_build_filename (root, "proc/sys/kernel/osrelease", NULL); ok = bolt_fs_make_parent_dirs (target, &err); if (!ok) { g_debug ("Failed to make parent dirs: %s", err->message); return FALSE; } if (version != NULL) data = g_strdup_printf ("%s\n", version); else data = g_strdup ("\n"); n = strlen (data); /* make sure we can write the file, if it fails, * we will indirectly catch it in the write */ path = g_file_get_path (target); r = chmod (path, 0644); if (r != 0) g_debug ("Failed to set mode: %s", g_strerror (errno)); ok = g_file_replace_contents (target, data, n, NULL, FALSE, 0, NULL, NULL, &err); if (!ok) g_debug ("Failed to set osrelease: %s", err->message); /* make the file non-readable if version == NULL, * so to simulate read errors */ if (version == NULL) { r = chmod (path, 0000); if (r != 0) g_debug ("Failed to set mode: %s", g_strerror (errno)); } return ok; } bolt-0.9.2/tests/mock-sysfs.h000066400000000000000000000103111417453051000160670ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include #include "bolt-enums.h" #include "bolt-wire.h" G_BEGIN_DECLS typedef struct MockDevId { gint vendor_id; const char *vendor_name; gint device_id; const char *device_name; const char *unique_id; } MockDevId; #define MOCK_TYPE_SYSFS mock_sysfs_get_type () G_DECLARE_FINAL_TYPE (MockSysfs, mock_sysfs, MOCK, SYSFS, GObject); MockSysfs * mock_sysfs_new (void); const char * mock_sysfs_force_power_add (MockSysfs *ms); gboolean mock_sysfs_force_power_remove (MockSysfs *ms); void mock_sysfs_force_power_load (MockSysfs *ms); void mock_sysfs_force_power_unload (MockSysfs *ms); char * mock_sysfs_force_power_read (MockSysfs *ms); gboolean mock_sysfs_force_power_enabled (MockSysfs *ms); const char * mock_sysfs_dmi_id_add (MockSysfs *ms, const char *sys_vendor, const char *product_name, const char *product_version); gboolean mock_sysfs_dmi_id_remove (MockSysfs *ms); const char * mock_sysfs_domain_add (MockSysfs *ms, BoltSecurity security, ...) G_GNUC_NULL_TERMINATED; const char * mock_sysfs_domain_get_syspath (MockSysfs *ms, const char *id); gboolean mock_sysfs_domain_remove (MockSysfs *ms, const char *id); GStrv mock_sysfs_domain_bootacl_get (MockSysfs *ms, const char *id, GError **error); gboolean mock_sysfs_domain_bootacl_set (MockSysfs *ms, const char *id, GStrv acl, GError **error); gboolean mock_syfs_domain_iommu_set (MockSysfs *ms, const char *id, const char *val, GError **error); const char * mock_sysfs_host_add (MockSysfs *ms, const char *domain, MockDevId *id); void mock_sysfs_host_remove (MockSysfs *ms, const char *host); const char * mock_sysfs_device_add (MockSysfs *ms, const char *parent, MockDevId *id, guint authorized, const char *key, gint boot, BoltLinkSpeed *link); const char * mock_sysfs_device_get_syspath (MockSysfs *ms, const char *id); const char * mock_sysfs_device_get_parent (MockSysfs *ms, const char *id); gboolean mock_sysfs_device_remove (MockSysfs *ms, const char *id); gboolean mock_sysfs_set_osrelease (MockSysfs *ms, const char *version); G_END_DECLS bolt-0.9.2/tests/pycodestyle.cfg000066400000000000000000000000441417453051000166470ustar00rootroot00000000000000[pycodestyle] max-line-length = 120 bolt-0.9.2/tests/test-auth.c000066400000000000000000000122051417453051000157060ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-auth.h" #include "bolt-device.h" #include "bolt-key.h" #include "bolt-dbus.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-io.h" #include "bolt-log.h" #include "bolt-str.h" #include "bolt-time.h" #include #include #include typedef struct TestDummy { int dummy; } TestDummy; static void test_auth_basic (TestDummy *tt, gconstpointer user_data) { g_autoptr(BoltAuth) auth = NULL; g_autoptr(BoltDevice) dev = NULL; g_autoptr(BoltDevice) d2 = NULL; g_autoptr(BoltKey) key = NULL; g_autoptr(BoltKey) k2 = NULL; g_autoptr(GObject) obj = NULL; BoltSecurity level; BoltPolicy policy; gpointer origin; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, NULL); g_assert_nonnull (dev); key = bolt_key_new (NULL); g_assert_nonnull (key); auth = bolt_auth_new (dev, BOLT_SECURITY_SECURE, key); g_object_set (auth, "device", dev, NULL); policy = BOLT_POLICY_DEFAULT; // we expect _UNKNOWN g_object_get (auth, "origin", &origin, "level", &level, "key", &k2, "device", &d2, "policy", &policy, NULL); g_assert_cmpint (level, ==, BOLT_SECURITY_SECURE); g_assert_true (origin == dev); g_assert_true (dev == bolt_auth_get_device (auth)); g_assert_true (key == k2); g_assert_true (dev == d2); g_assert_cmpint (policy, ==, BOLT_POLICY_UNKNOWN); g_assert_true (G_IS_ASYNC_RESULT (auth)); obj = g_async_result_get_source_object (G_ASYNC_RESULT (auth)); g_assert ((gpointer) obj == (gpointer) dev); g_assert_false (g_async_result_is_tagged (G_ASYNC_RESULT (auth), &obj)); g_assert_null (g_async_result_get_user_data (G_ASYNC_RESULT (auth))); bolt_auth_set_policy (auth, BOLT_POLICY_MANUAL); policy = bolt_auth_get_policy (auth); g_assert_cmpint (policy, ==, BOLT_POLICY_MANUAL); g_object_set (auth, "policy", BOLT_POLICY_AUTO, NULL); policy = BOLT_POLICY_UNKNOWN; g_object_get (auth, "policy", &policy, NULL); g_assert_cmpint (policy, ==, BOLT_POLICY_AUTO); } static void test_auth_error (TestDummy *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(BoltAuth) auth = NULL; g_autoptr(BoltDevice) dev = NULL; g_autoptr(BoltKey) key = NULL; // BoltSecurity level; //gpointer origin; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; gboolean ok; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, NULL); g_assert_nonnull (dev); key = bolt_key_new (NULL); g_assert_nonnull (key); auth = bolt_auth_new (dev, BOLT_SECURITY_SECURE, key); bolt_auth_return_new_error (auth, BOLT_ERROR, BOLT_ERROR_BADSTATE, "we are in a bad state: %s", "depressed"); ok = bolt_auth_check (auth, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_BADSTATE); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* error should be copied, so we should be able to call it twice */ ok = bolt_auth_check (auth, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_BADSTATE); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* */ g_clear_object (&auth); auth = bolt_auth_new (dev, BOLT_SECURITY_SECURE, key); g_set_error (&err, BOLT_ERROR, BOLT_ERROR_AUTHCHAIN, "we are in a bad state: %s", "depressed"); bolt_auth_return_error (auth, &err); g_assert_no_error (err); ok = bolt_auth_check (auth, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_AUTHCHAIN); g_assert_false (ok); g_clear_pointer (&err, g_error_free); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/auth/basic", TestDummy, NULL, NULL, test_auth_basic, NULL); g_test_add ("/auth/error", TestDummy, NULL, NULL, test_auth_error, NULL); return g_test_run (); } bolt-0.9.2/tests/test-common.c000066400000000000000000002231721417453051000162440ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-dbus.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-io.h" #include "bolt-list.h" #include "bolt-rnd.h" #include "bolt-str.h" #include "bolt-term.h" #include "bolt-test.h" #include "bolt-time.h" #include "bolt-unix.h" #include "mock-sysfs.h" #include "test-enums.h" #include "bolt-test-resources.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* unlinkat, fork */ #if !GLIB_CHECK_VERSION (2, 57, 0) G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref); G_DEFINE_AUTOPTR_CLEANUP_FUNC (GFlagsClass, g_type_class_unref); #endif typedef struct { int dummy; } TestDummy; /* test tables for string to number conversions */ struct { const char *str; gint val; gboolean error; } str_to_int_table[] = { {"0", 0, FALSE}, {"1", 1, FALSE}, {"-1", -1, FALSE}, #if __SIZEOF_INT__ == 4 {"2147483647", 2147483647, FALSE}, /* MAX_INT */ {"-2147483648", -2147483648, FALSE}, /* MIN_INT */ {"2147483648", 0, TRUE}, /* MAX_INT + 1 */ {"-2147483649", 0, TRUE}, /* MIN_INT - 1 */ #elif __SIZEOF_INT__ == 8 {"9223372036854775807", 9223372036854775807, FALSE}, /* MAX_INT */ {"-9223372036854775808", -9223372036854775808, FALSE}, /* MIN_INT */ {"9223372036854775808", 0, TRUE}, /* MAX_INT + 1 */ {"-9223372036854775809", 0, TRUE}, /* MIN_INT - 1 */ #else #warning __SIZEOF_INT__ not handled #endif {"notanint", 0, TRUE}, {"9223372036854775808", 0, TRUE}, /* overflow */ {"-9223372036854775809", 0, TRUE}, /* underflow */ }; struct { const char *str; guint val; gboolean error; } str_to_uint_table[] = { {"0", 0, FALSE}, {"1", 1, FALSE}, {"-1", 0, TRUE}, /* negative */ #if __SIZEOF_INT__ == 4 {"4294967295", 4294967295, FALSE}, /* MAX_UINT */ {"4294967296", 0, TRUE}, /* MAX_UINT + 1 */ #elif __SIZEOF_INT__ == 8 {"18446744073709551615", 18446744073709551615, FALSE}, /* MAX_INT */ {"18446744073709551616", 0, TRUE}, /* MAX_INT + 1 */ #else #warning __SIZEOF_INT__ not handled #endif {"notanint", 0, TRUE}, {"18446744073709551617", 0, TRUE}, /* overflow */ }; #define TEST_DBUS_GRESOURCE_PATH "/bolt/tests/exported/example.bolt.xml" #define TEST_DBUS_INTERFACE "org.gnome.bolt.Example" static void test_dbus_interface_info_find (TestDummy *tt, gconstpointer user_data) { g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; GDBusInterfaceInfo *info = NULL; const char *xml; data = g_resources_lookup_data (TEST_DBUS_GRESOURCE_PATH, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); g_assert_no_error (error); g_assert_nonnull (data); xml = g_bytes_get_data (data, NULL); info = bolt_dbus_interface_info_find (xml, "NON-EXISTENT", &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (info); g_clear_error (&error); info = bolt_dbus_interface_info_find (xml, TEST_DBUS_INTERFACE, &error); g_assert_no_error (error); g_assert_nonnull (info); g_dbus_interface_info_unref (info); } static void test_dbus_interface_info_lookup (TestDummy *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; GDBusInterfaceInfo *info = NULL; info = bolt_dbus_interface_info_lookup ("NON-EXISTENT", "NON-EXISTENT", &error); g_assert_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND); g_assert_null (info); g_clear_error (&error); info = bolt_dbus_interface_info_lookup (TEST_DBUS_GRESOURCE_PATH, "NON-EXISTENT", &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (info); g_clear_error (&error); info = bolt_dbus_interface_info_lookup (TEST_DBUS_GRESOURCE_PATH, TEST_DBUS_INTERFACE, &error); g_assert_no_error (error); g_assert_nonnull (info); g_dbus_interface_info_unref (info); } typedef struct { int dummy; } TestRng; static void test_enums (TestRng *tt, gconstpointer user_data) { g_autoptr(GEnumClass) klass; g_autoptr(GError) err = NULL; const char *str; gint val; gboolean ok; struct EnumTest { GType enum_type; const char *name; gint value; } ett[] = { {BOLT_TYPE_SECURITY, "none", BOLT_SECURITY_NONE}, {BOLT_TYPE_SECURITY, "dponly", BOLT_SECURITY_DPONLY}, {BOLT_TYPE_SECURITY, "user", BOLT_SECURITY_USER}, {BOLT_TYPE_SECURITY, "secure", BOLT_SECURITY_SECURE}, {BOLT_TYPE_TEST_ENUM, "unknown", BOLT_TEST_UNKNOWN}, {BOLT_TYPE_TEST_ENUM, "one", BOLT_TEST_ONE}, {BOLT_TYPE_TEST_ENUM, "two", BOLT_TEST_TWO}, {BOLT_TYPE_TEST_ENUM, "three", BOLT_TEST_THREE}, }; for (guint i = 0; i < G_N_ELEMENTS (ett); i++) { ok = bolt_enum_validate (ett[i].enum_type, ett[i].value, &err); g_assert_no_error (err); g_assert_true (ok); /* to string */ str = bolt_enum_to_string (ett[i].enum_type, ett[i].value, &err); g_assert_no_error (err); g_assert_nonnull (str); g_assert_cmpstr (str, ==, ett[i].name); /* from string */ val = bolt_enum_from_string (ett[i].enum_type, ett[i].name, &err); g_assert_no_error (err); g_assert_cmpint (val, ==, ett[i].value); } g_assert_cmpstr (bolt_security_to_string (BOLT_SECURITY_NONE), ==, "none"); g_assert_cmpstr (bolt_security_to_string (BOLT_SECURITY_DPONLY), ==, "dponly"); g_assert_cmpstr (bolt_security_to_string (BOLT_SECURITY_USER), ==, "user"); g_assert_cmpstr (bolt_security_to_string (BOLT_SECURITY_SECURE), ==, "secure"); g_assert_cmpuint (bolt_security_from_string ("none"), ==, BOLT_SECURITY_NONE); g_assert_cmpuint (bolt_security_from_string ("dponly"), ==, BOLT_SECURITY_DPONLY); g_assert_cmpuint (bolt_security_from_string ("user"), ==, BOLT_SECURITY_USER); g_assert_cmpuint (bolt_security_from_string ("secure"), ==, BOLT_SECURITY_SECURE); klass = g_type_class_ref (BOLT_TYPE_SECURITY); ok = bolt_enum_class_validate (klass, klass->minimum, &err); g_assert_no_error (err); g_assert_true (ok); ok = bolt_enum_class_validate (klass, klass->maximum, &err); g_assert_no_error (err); g_assert_true (ok); str = bolt_enum_to_string (BOLT_TYPE_SECURITY, klass->minimum, &err); g_assert_no_error (err); g_assert_nonnull (str); str = bolt_enum_to_string (BOLT_TYPE_SECURITY, klass->maximum, &err); g_assert_no_error (err); g_assert_nonnull (str); ok = bolt_enum_class_validate (klass, klass->maximum + 1, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); ok = bolt_enum_class_validate (klass, klass->minimum - 1, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); ok = bolt_enum_validate (BOLT_TYPE_SECURITY, -42, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); str = bolt_enum_to_string (BOLT_TYPE_SECURITY, -42, &err); g_assert_nonnull (err); g_assert_null (str); g_clear_error (&err); val = bolt_enum_from_string (BOLT_TYPE_SECURITY, "ILEDELI", &err); g_assert_nonnull (err); g_assert_cmpint (val, ==, -1); g_clear_error (&err); } static void test_error (TestRng *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(GError) failed = NULL; g_autoptr(GError) notfound = NULL; g_autoptr(GError) exists = NULL; g_autoptr(GError) inval = NULL; g_autoptr(GError) cancelled = NULL; g_autoptr(GError) badstate = NULL; g_autoptr(GError) target = NULL; g_autoptr(GError) source = NULL; g_autoptr(GError) noerror = NULL; g_autoptr(GError) buserr = NULL; g_autofree char *remote = NULL; gboolean ok; g_set_error_literal (&failed, BOLT_ERROR, BOLT_ERROR_FAILED, "operation failed"); /* bolt_err_notfound */ g_set_error_literal (¬found, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "not found"); g_assert_false (bolt_err_notfound (failed)); g_assert_true (bolt_err_notfound (notfound)); /* bolt_err_exists */ g_set_error_literal (&exists, G_IO_ERROR, G_IO_ERROR_EXISTS, "already exists"); g_assert_false (bolt_err_exists (failed)); g_assert_true (bolt_err_exists (exists)); /* (bolt_err_inval */ g_set_error_literal (&inval, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid argument"); g_assert_false (bolt_err_inval (failed)); g_assert_true (bolt_err_inval (inval)); /* bolt_err_cancelled */ g_set_error_literal (&cancelled, G_IO_ERROR, G_IO_ERROR_CANCELLED, "cancelled"); g_assert_false (bolt_err_cancelled (failed)); g_assert_true (bolt_err_cancelled (cancelled)); /* bolt_err_badstate */ g_set_error_literal (&badstate, BOLT_ERROR, BOLT_ERROR_BADSTATE, "bad state"); g_assert_false (bolt_err_badstate (failed)); g_assert_true (bolt_err_badstate (badstate)); /* bolt_error_propagate */ ok = bolt_error_propagate (NULL, &noerror); g_assert_true (ok); g_assert_no_error (target); ok = bolt_error_propagate (&target, &noerror); g_assert_no_error (target); g_assert_true (ok); g_set_error_literal (&source, BOLT_ERROR, BOLT_ERROR_FAILED, "operation failed"); g_assert_no_error (target); g_assert_error (source, BOLT_ERROR, BOLT_ERROR_FAILED); ok = bolt_error_propagate (&target, &source); g_assert_no_error (source); g_assert_error (target, BOLT_ERROR, BOLT_ERROR_FAILED); g_assert_false (ok); /* and back */ ok = bolt_error_propagate (&source, &target); g_assert_no_error (target); g_assert_error (source, BOLT_ERROR, BOLT_ERROR_FAILED); g_assert_false (ok); /* bolt_error_propagate_stripped */ ok = bolt_error_propagate_stripped (NULL, &noerror); g_assert_true (ok); g_assert_no_error (target); ok = bolt_error_propagate_stripped (&target, &noerror); g_assert_no_error (target); g_assert_true (ok); /* normal error */ ok = bolt_error_propagate_stripped (&target, &source); g_assert_no_error (source); g_assert_error (target, BOLT_ERROR, BOLT_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&target, g_error_free); /* bus error */ g_set_error_literal (&buserr, BOLT_ERROR, BOLT_ERROR_BADKEY, "such a bad, bad key"); remote = g_dbus_error_get_remote_error (buserr); source = g_dbus_error_new_for_dbus_error (remote, buserr->message); g_assert_error (source, BOLT_ERROR, BOLT_ERROR_BADKEY); g_assert_true (g_dbus_error_is_remote_error (source)); ok = bolt_error_propagate_stripped (&target, &source); g_assert_error (target, BOLT_ERROR, BOLT_ERROR_BADKEY); g_assert_false (ok); g_assert_false (g_dbus_error_is_remote_error (target)); g_assert_cmpstr (target->message, ==, buserr->message); /* bolt_error_for_errno */ ok = bolt_error_for_errno (NULL, 0, "no error!"); g_assert_true (ok); ok = bolt_error_for_errno (&error, 0, "no error!"); g_assert_no_error (error); g_assert_true (ok); ok = bolt_error_for_errno (NULL, ENOENT, "no such thing"); g_assert_false (ok); ok = bolt_error_for_errno (&error, ENOENT, "no such thing"); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_true (g_str_has_prefix (error->message, "no such thing")); g_assert_false (ok); g_clear_pointer (&error, g_error_free); ok = bolt_error_for_errno (&error, ENOENT, "%m"); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_debug ("ENOENT formatted via %%m is '%s'", error->message); g_assert_true (strlen (error->message) > 0); g_assert_false (ok); g_clear_pointer (&error, g_error_free); } static void test_flags (TestRng *tt, gconstpointer user_data) { g_autoptr(GFlagsClass) klass; g_autoptr(GError) err = NULL; char *str; guint val; guint ref; gboolean ok; gboolean chg; struct EnumTest { GType flags_type; const char *name; guint value; } ftt[] = { {BOLT_TYPE_KITT_FLAGS, "disabled", BOLT_KITT_DISABLED}, {BOLT_TYPE_KITT_FLAGS, "enabled", BOLT_KITT_ENABLED}, {BOLT_TYPE_KITT_FLAGS, "sspm", BOLT_KITT_SSPM}, {BOLT_TYPE_KITT_FLAGS, "turbo-boost", BOLT_KITT_TURBO_BOOST}, {BOLT_TYPE_KITT_FLAGS, "ski-mode", BOLT_KITT_SKI_MODE}, {BOLT_TYPE_KITT_FLAGS, "enabled | ski-mode", BOLT_KITT_ENABLED | BOLT_KITT_SKI_MODE}, {BOLT_TYPE_KITT_FLAGS, "sspm | turbo-boost | ski-mode", BOLT_KITT_SSPM | BOLT_KITT_SKI_MODE | BOLT_KITT_TURBO_BOOST}, }; for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) { g_autofree char *s = NULL; /* to string */ s = bolt_flags_to_string (ftt[i].flags_type, ftt[i].value, &err); g_assert_no_error (err); g_assert_nonnull (s); g_assert_cmpstr (s, ==, ftt[i].name); /* from string */ ok = bolt_flags_from_string (ftt[i].flags_type, ftt[i].name, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpuint (val, ==, ftt[i].value); } /* handle invalid values */ klass = g_type_class_ref (BOLT_TYPE_KITT_FLAGS); g_assert_nonnull (klass); ok = bolt_flags_class_from_string (klass, NULL, &val, &err); g_assert_error (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); g_assert_false (ok); g_clear_error (&err); ok = bolt_flags_class_from_string (klass, "fax-machine", &val, &err); g_assert_error (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); g_assert_false (ok); g_clear_error (&err); str = bolt_flags_class_to_string (klass, 0xFFFF, &err); g_assert_error (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); g_assert_null (str); g_clear_error (&err); str = bolt_flags_class_to_string (klass, BOLT_KITT_SKI_MODE << 1, &err); g_assert_error (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); g_assert_null (str); g_clear_error (&err); /* there and back again */ ref = BOLT_KITT_SSPM | BOLT_KITT_SKI_MODE | BOLT_KITT_TURBO_BOOST; str = bolt_flags_class_to_string (klass, ref, &err); g_assert_no_error (err); g_assert_nonnull (str); ok = bolt_flags_class_from_string (klass, str, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpuint (val, ==, ref); g_clear_pointer (&str, g_free); /* handle "" and 0 */ ok = bolt_flags_class_from_string (klass, "", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpuint (val, ==, BOLT_KITT_DISABLED); str = bolt_flags_class_to_string (klass, 0, &err); g_assert_no_error (err); g_assert_nonnull (str); g_assert_cmpstr (str, ==, "disabled"); g_clear_pointer (&str, g_free); /* values as compositions */ ok = bolt_flags_class_from_string (klass, "default", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpuint (val, ==, BOLT_KITT_DEFAULT); g_assert_cmpuint (val, ==, BOLT_KITT_ENABLED | BOLT_KITT_SSPM); ref = BOLT_KITT_ENABLED | BOLT_KITT_SSPM; str = bolt_flags_class_to_string (klass, ref, &err); g_assert_no_error (err); g_assert_nonnull (str); g_assert_true (strstr (str, "enabled")); g_assert_true (strstr (str, "sspm")); g_clear_pointer (&str, g_free); /* test flags updating */ val = 0; chg = bolt_flags_update (0, &val, 0); g_assert_false (chg); /* no updates */ val = 0; ref = BOLT_KITT_SSPM | BOLT_KITT_SKI_MODE | BOLT_KITT_TURBO_BOOST; chg = bolt_flags_update (ref, &val, 0); g_assert_false (chg); val = BOLT_KITT_SSPM | BOLT_KITT_SKI_MODE | BOLT_KITT_TURBO_BOOST; chg = bolt_flags_update (ref, &val, 0); g_assert_false (chg); chg = bolt_flags_update (ref, &val, val); g_assert_false (chg); /* finally, some updates */ val = 0; ref = BOLT_KITT_SSPM | BOLT_KITT_SKI_MODE | BOLT_KITT_TURBO_BOOST; chg = bolt_flags_update (ref, &val, BOLT_KITT_SSPM); g_assert_true (chg); g_assert_cmpuint (val, ==, BOLT_KITT_SSPM); val = 0; chg = bolt_flags_update (ref, &val, BOLT_KITT_TURBO_BOOST); g_assert_true (chg); g_assert_cmpuint (val, ==, BOLT_KITT_TURBO_BOOST); val = BOLT_KITT_SSPM; ref = BOLT_KITT_TURBO_BOOST; chg = bolt_flags_update (ref, &val, BOLT_KITT_TURBO_BOOST); g_assert_true (chg); ref = BOLT_KITT_TURBO_BOOST | BOLT_KITT_SSPM; g_assert_cmpuint (val, ==, ref); val = BOLT_KITT_TURBO_BOOST | BOLT_KITT_SSPM; ref = 0; chg = bolt_flags_update (ref, &val, BOLT_KITT_TURBO_BOOST); g_assert_cmpuint (val, ==, BOLT_KITT_SSPM); g_assert_true (chg); /* simple checks for helper class */ ref = BOLT_KITT_TURBO_BOOST | BOLT_KITT_SSPM; g_assert_true (bolt_flag_isset (ref, BOLT_KITT_TURBO_BOOST)); g_assert_true (bolt_flag_isset (ref, BOLT_KITT_SSPM)); g_assert_false (bolt_flag_isclear (ref, BOLT_KITT_TURBO_BOOST)); g_assert_false (bolt_flag_isset (ref, BOLT_KITT_SKI_MODE)); g_assert_true (bolt_flag_isclear (ref, BOLT_KITT_SKI_MODE)); } typedef void (*rng_t) (void *buf, gsize n); #define RNG_COUNT 258 static guint test_rng_loop (guint N, rng_t fn) { char buf[RNG_COUNT] = { 0, }; guint count[RNG_COUNT] = {0, }; guint hits = 0; for (guint n = 0; n < N; n++) { memset (buf, 0, sizeof (buf)); fn (buf, sizeof (buf)); for (guint i = 0; i < RNG_COUNT; i++) if (buf[i] == 0) count[i]++; } for (guint i = 0; i < RNG_COUNT; i++) hits = MAX (hits, count[i]); return hits; } static void no_rng (void *buf, gsize n) { /* noop */ } #if HAVE_FN_GETRANDOM static void getrandom_rng (void *buf, gsize n) { g_autoptr(GError) error = NULL; gboolean ok; ok = bolt_random_getrandom (buf, n, 0, &error); g_assert_no_error (error); g_assert_true (ok); } #endif static void test_rng (TestRng *tt, gconstpointer user_data) { char buf[10] = {0, }; guint hits; gboolean ok; static guint N = 10; hits = test_rng_loop (N, no_rng); g_assert_cmpuint (hits, ==, 10); hits = test_rng_loop (N, bolt_random_prng); g_assert_cmpuint (hits, <, 10); hits = test_rng_loop (N, (rng_t) bolt_get_random_data); g_assert_cmpuint (hits, <, 10); ok = bolt_random_urandom (buf, sizeof (buf)); if (ok) { hits = test_rng_loop (N, (rng_t) bolt_random_urandom); g_assert_cmpuint (hits, <, 10); } else { g_debug ("urandom RNG seems to not be working"); } #if HAVE_FN_GETRANDOM g_debug ("testing getrandom"); hits = test_rng_loop (N, (rng_t) getrandom_rng); g_assert_cmpuint (hits, <, 10); #else g_debug ("getrandom RNG not available"); #endif } typedef struct { char *path; } TestIO; static void test_io_setup (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; tt->path = g_dir_make_tmp ("bolt.io.XXXXXX", &error); if (tt->path == NULL) { g_critical ("Could not create tmp dir: %s", error->message); return; } } static void test_io_tear_down (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; gboolean ok; ok = bolt_fs_cleanup_dir (tt->path, &error); if (!ok && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) g_warning ("Could not clean up dir: %s", error->message); g_clear_pointer (&tt->path, g_free); } static const char *valid_uid = "f96b4cc77f196068ec454cb6006514c602d1011f47dd275cf5c6b8a47744f049"; static void test_io_errors (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(GFile) empty_file = NULL; g_autoptr(DIR) root = NULL; g_autoptr(DIR) dp = NULL; g_autofree char *noexist = NULL; g_autofree char *subdir = NULL; g_autofree char *rdonly = NULL; g_autofree char *empty = NULL; g_autofree char *fifo = NULL; g_autofree char *data = NULL; bolt_autoclose int to = -1; bolt_autoclose int from = -1; struct stat st; char buffer[256] = {0, }; gboolean ok; unsigned int uiv; int fd = -1; int iv; int r; /* preparation */ root = bolt_opendir (tt->path, &err); g_assert_no_error (err); g_assert_nonnull (root); noexist = g_build_filename (tt->path, "NONEXISTENT", NULL); subdir = g_build_filename (tt->path, "subdir", NULL); rdonly = g_build_filename (tt->path, "readonly", NULL); ok = g_file_set_contents (rdonly, "Hallo Welt", -1, &err); g_assert_no_error (err); g_assert_true (ok); r = chmod (rdonly, 0400); g_assert_cmpint (r, >, -1); empty = g_build_filename (tt->path, "empty", NULL); empty_file = g_file_new_for_path (empty); ok = bolt_fs_touch (empty_file, 0, 0, &err); g_assert_no_error (err); g_assert_true (ok); /* error handling*/ fd = bolt_open (noexist, O_RDONLY | O_CLOEXEC | O_NOCTTY, 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_cmpint (fd, <, 0); g_clear_pointer (&err, g_error_free); ok = bolt_close (fd, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* create a pipe, so we can have some read and write errors * by using the wrong end */ fifo = g_build_filename (tt->path, "fifo", NULL); r = bolt_mkfifo (fifo, 0600, &err); g_assert_no_error (err); g_assert_cmpint (r, ==, 0); /* reader */ from = bolt_open (fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK, 0, &err); g_assert_no_error (err); g_assert_cmpint (from, >, -1); /* writer */ to = bolt_open (fifo, O_WRONLY | O_CLOEXEC | O_NONBLOCK, 0, &err); g_assert_no_error (err); g_assert_cmpint (to, >, -1); ok = bolt_read_all (to, buffer, sizeof (buffer), NULL, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); ok = bolt_write_all (from, buffer, sizeof (buffer), &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* ftruncate */ ok = bolt_ftruncate (to, 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* opendir error checking */ dp = bolt_opendir (noexist, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (dp); g_clear_pointer (&err, g_error_free); /* opendir_at error checking */ dp = bolt_opendir_at (dirfd (root), "NONEXISTENT", 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (dp); g_clear_pointer (&err, g_error_free); dp = bolt_opendir_at (dirfd (root), "fifo", 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY); g_assert_null (dp); g_clear_pointer (&err, g_error_free); /* closedir */ #ifdef __GLIBC__ ok = bolt_closedir (NULL, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_pointer (&err, g_error_free); #endif /* rmdir */ ok = bolt_rmdir (noexist, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* openat */ fd = bolt_openat (dirfd (root), "NONEXISTENT", 0, 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_cmpint (fd, <, 0); g_clear_pointer (&err, g_error_free); /* unlink error checking */ ok = bolt_unlink (noexist, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* unlink_at */ ok = bolt_unlink_at (dirfd (root), "NONEXISTENT", 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* read_value_at */ data = bolt_read_value_at (dirfd (root), "NONEXISTENT", &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (data); g_clear_pointer (&err, g_error_free); data = bolt_read_value_at (dirfd (root), "empty", &err); g_assert_no_error (err); g_assert_cmpstr (data, ==, ""); /* write char at */ ok = bolt_write_char_at (dirfd (root), "NONEXISTENT", 'c', &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); if (geteuid () != 0) { /* if we run as root, maybe inside a container, we will * be able o do that anyway so skip it in that case */ ok = bolt_write_char_at (dirfd (root), "readonly", 'c', &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); } /* read_int_at */ ok = bolt_read_int_at (dirfd (root), "NONEXISTENT", &iv, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); ok = bolt_read_int_at (dirfd (root), "readonly", &iv, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* read_uint_at */ ok = bolt_read_uint_at (dirfd (root), "NONEXISTENT", &uiv, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); ok = bolt_read_uint_at (dirfd (root), "readonly", &uiv, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* pipe error checking */ fd = bolt_mkfifo (tt->path, 0600, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_EXISTS); g_assert_cmpint (fd, <, 0); g_clear_pointer (&err, g_error_free); /* faddflags */ ok = bolt_faddflags (-1, 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* fstat */ ok = bolt_fstat (-1, &st, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* fstatat */ ok = bolt_fstatat (dirfd (root), "NONEXISTENT", &st, 0, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* fdatasync */ ok = bolt_fdatasync (-1, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* lseek */ ok = bolt_lseek (to, 0, SEEK_SET, NULL, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* rename */ ok = bolt_rename (noexist, subdir, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* copy_bytes */ ok = bolt_copy_bytes (to, from, 1, &err); g_assert_nonnull (err); g_assert_cmpint (err->domain, ==, G_IO_ERROR); g_assert_false (ok); g_clear_pointer (&err, g_error_free); } static void test_io_mkdirat (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(DIR) d = NULL; struct stat st; gboolean ok; d = bolt_opendir (tt->path, &error); g_assert_no_error (error); g_assert_nonnull (d); ok = bolt_mkdirat (dirfd (d), "directory", 0666, &error); g_assert_no_error (error); g_assert_true (ok); ok = bolt_fstatat (dirfd (d), "directory", &st, 0, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_true (S_ISDIR (st.st_mode)); ok = bolt_mkdirat (dirfd (d), "directory", 0666, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); g_assert_false (ok); } static void test_io_verify (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(DIR) d = NULL; g_autofree char *uid_path = NULL; gboolean ok; /* more preparation*/ d = bolt_opendir (tt->path, &error); g_assert_nonnull (d); g_assert_no_error (error); /* unique_id missing */ ok = bolt_verify_uid (dirfd (d), valid_uid, &error); g_assert_false (ok); g_assert_error (error, BOLT_ERROR, BOLT_ERROR_FAILED); g_clear_error (&error); /* existing, but wrong value */ uid_path = g_build_filename (tt->path, "unique_id", NULL); ok = g_file_set_contents (uid_path, "wrong_to_small", -1, &error); g_assert_true (ok); g_assert_no_error (error); /* must fail */ ok = bolt_verify_uid (dirfd (d), valid_uid, &error); g_assert_false (ok); g_assert_error (error, BOLT_ERROR, BOLT_ERROR_FAILED); g_clear_error (&error); ok = g_file_set_contents (uid_path, valid_uid, -1, &error); g_assert_true (ok); g_assert_no_error (error); /* must work */ ok = bolt_verify_uid (dirfd (d), valid_uid, &error); g_assert_true (ok); g_assert_no_error (error); g_clear_error (&error); unlinkat (dirfd (d), "unique_id", 0); } static void test_io_write_file_at (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(DIR) dir = NULL; g_autofree char *path = NULL; g_autofree char *data = NULL; static const char *ref = "The world is everything that is the case."; gboolean ok; gsize len; dir = bolt_opendir (tt->path, &error); g_assert_no_error (error); g_assert_nonnull (dir); ok = bolt_write_file_at (dirfd (dir), "test.txt", ref, -1, &error); g_assert_no_error (error); g_assert_true (ok); path = g_build_filename (tt->path, "test.txt", NULL); ok = g_file_get_contents (path, &data, &len, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (strlen (ref), ==, len); g_assert_cmpstr (ref, ==, data); g_clear_pointer (&data, g_free); ok = bolt_file_write_all (path, ref, 5, &error); g_assert_no_error (error); g_assert_true (ok); ok = g_file_get_contents (path, &data, &len, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (len, ==, 5); g_assert_true (strncmp (data, ref, 5) == 0); } static void test_io_write_int_at (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(DIR) dir = NULL; gboolean ok; int tests[] = {0, 1, 42, G_MAXINT}; dir = bolt_opendir (tt->path, &error); g_assert_no_error (error); g_assert_nonnull (dir); for (gsize i = 0; i < G_N_ELEMENTS (tests); i++) { g_autoptr(GError) err = NULL; int ref = tests[i]; int val; ok = bolt_write_int_at (dirfd (dir), "int.txt", ref, &err); g_assert_true (ok); ok = bolt_read_int_at (dirfd (dir), "int.txt", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (val, ==, ref); } } static void test_io_read_int_at (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(DIR) dir = NULL; gboolean ok; dir = bolt_opendir (tt->path, &error); g_assert_no_error (error); g_assert_nonnull (dir); for (gsize i = 0; i < G_N_ELEMENTS (str_to_int_table); i++) { g_autoptr(GError) err = NULL; const char *txt = str_to_int_table[i].str; gboolean expect_error = str_to_int_table[i].error; gint val = str_to_int_table[i].val; gint v; ok = bolt_write_file_at (dirfd (dir), "int.txt", txt, -1, &err); g_assert_true (ok); ok = bolt_read_int_at (dirfd (dir), "int.txt", &v, &err); if (expect_error) { g_assert_nonnull (err); g_assert_false (ok); } else { g_assert_no_error (err); g_assert_cmpint (val, ==, v); g_assert_true (ok); } } } static void test_io_write_uint_at (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(DIR) dir = NULL; gboolean ok; unsigned int tests[] = {42, 0, 1, G_MAXUINT}; dir = bolt_opendir (tt->path, &error); g_assert_no_error (error); g_assert_nonnull (dir); for (gsize i = 0; i < G_N_ELEMENTS (tests); i++) { g_autoptr(GError) err = NULL; unsigned int ref = tests[i]; unsigned int val; ok = bolt_write_uint_at (dirfd (dir), "uint.txt", ref, &err); g_assert_true (ok); ok = bolt_read_uint_at (dirfd (dir), "uint.txt", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (val, ==, ref); } } static void test_io_read_uint_at (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(DIR) dir = NULL; gboolean ok; dir = bolt_opendir (tt->path, &error); g_assert_no_error (error); g_assert_nonnull (dir); for (gsize i = 0; i < G_N_ELEMENTS (str_to_uint_table); i++) { g_autoptr(GError) err = NULL; const char *txt = str_to_uint_table[i].str; gboolean expect_error = str_to_uint_table[i].error; guint val = str_to_uint_table[i].val; guint v; if (g_test_verbose ()) g_test_message ("bolt_read_uint: '%s'", txt); ok = bolt_write_file_at (dirfd (dir), "uint.txt", txt, -1, &err); g_assert_true (ok); ok = bolt_read_uint_at (dirfd (dir), "uint.txt", &v, &err); if (expect_error) { g_assert_nonnull (err); g_assert_false (ok); } else { g_assert_no_error (err); g_assert_cmpuint (val, ==, v); g_assert_true (ok); } } } static void test_io_file_write_all (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autofree char *path = NULL; g_autofree char *data = NULL; static const char *ref = "The world is everything that is the case."; gboolean ok; gsize len; path = g_build_filename (tt->path, "file_write_all", NULL); ok = bolt_file_write_all (path, ref, -1, &error); g_assert_no_error (error); g_assert_true (ok); ok = g_file_get_contents (path, &data, &len, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (strlen (ref), ==, len); g_assert_cmpstr (ref, ==, data); g_clear_pointer (&data, g_free); ok = bolt_file_write_all (path, ref, 5, &error); g_assert_no_error (error); g_assert_true (ok); ok = g_file_get_contents (path, &data, &len, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (len, ==, 5); g_assert_true (strncmp (data, ref, 5) == 0); } static void test_io_renameat (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(DIR) root = NULL; g_autoptr(DIR) subdir = NULL; struct stat st; gboolean ok; root = bolt_opendir (tt->path, &err); g_assert_no_error (err); g_assert_nonnull (root); ok = bolt_write_file_at (dirfd (root), "a", "a", -1, &err); g_assert_no_error (err); g_assert_true (ok); ok = bolt_mkdirat (dirfd (root), "subdir", 0777, &err); g_assert_no_error (err); g_assert_true (ok); subdir = bolt_opendir_at (dirfd (root), "subdir", O_RDONLY, &err); g_assert_no_error (err); g_assert_nonnull (subdir); ok = bolt_renameat (dirfd (root), "a", dirfd (subdir), "b", &err); g_assert_no_error (err); g_assert_true (ok); ok = bolt_fstatat (dirfd (subdir), "b", &st, 0, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (S_ISREG (st.st_mode)); ok = bolt_renameat (dirfd (subdir), "b", dirfd (subdir), "c", &err); g_assert_no_error (err); g_assert_true (ok); memset (&st, 0, sizeof (st)); ok = bolt_fstatat (dirfd (subdir), "c", &st, 0, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (S_ISREG (st.st_mode)); /* error reporting: file not found */ ok = bolt_renameat (dirfd (subdir), "b", dirfd (subdir), "c", &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); } static void test_io_copy_bytes (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(GChecksum) chk = NULL; g_autofree char *source = NULL; g_autofree char *target = NULL; g_autofree char *chksum = NULL; const gsize N = 1024; char buf[4096]; gboolean ok; int from = -1; int to = -1; /* bolt_copy_bytes uses copy_file_range(2) internally, which * was added in linux 4.5. */ skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), "linux kernel < 4.5, copy_file_range syscall missing"); chk = g_checksum_new (G_CHECKSUM_SHA256); source = g_build_filename (tt->path, "copy_bytes_source", NULL); from = bolt_open (source, O_RDWR | O_CREAT, 0666, &error); g_assert_no_error (error); g_assert_cmpint (from, >, -1); for (gsize i = 0; i < N; i++) { bolt_random_prng (buf, sizeof (buf)); ok = bolt_write_all (from, buf, sizeof (buf), &error); g_assert_no_error (error); g_assert_true (ok); g_checksum_update (chk, (const guchar *) buf, sizeof (buf)); } chksum = g_strdup (g_checksum_get_string (chk)); /* close, reopen readonly */ ok = bolt_close (from, &error); g_assert_no_error (error); g_assert_true (ok); from = bolt_open (source, O_CLOEXEC | O_RDONLY, 0, &error); g_assert_no_error (error); g_assert_cmpint (from, >, -1); /* copy the data with bolt_copy_bytes */ target = g_build_filename (tt->path, "copy_bytes_target", NULL); to = bolt_open (target, O_RDWR | O_CREAT, 0666, &error); g_assert_no_error (error); g_assert_cmpint (to, >, -1); ok = bolt_copy_bytes (from, to, N * sizeof (buf), &error); g_assert_no_error (error); g_assert_true (ok); ok = bolt_close (from, &error); g_assert_no_error (error); g_assert_true (ok); /* close, reopen, check checksum */ ok = bolt_close (to, &error); g_assert_no_error (error); g_assert_true (ok); to = bolt_open (target, O_CLOEXEC | O_RDONLY, 0, &error); g_assert_no_error (error); g_assert_cmpint (to, >, -1); g_checksum_reset (chk); for (gsize i = 0; i < N; i++) { gsize nread = 0; bolt_read_all (to, buf, sizeof (buf), &nread, &error); g_assert_no_error (error); g_checksum_update (chk, (const guchar *) buf, nread); } ok = bolt_close (to, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpstr (g_checksum_get_string (chk), ==, chksum); } static void test_io_dir_is_empty (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(DIR) root = NULL; struct dirent *de = NULL; gboolean empty; gboolean ok; long pos; root = bolt_opendir (tt->path, &err); g_assert_no_error (err); g_assert_nonnull (root); empty = FALSE; ok = bolt_dir_is_empty (root, &empty, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (empty); ok = bolt_write_file_at (dirfd (root), "a", "a", -1, &err); g_assert_no_error (err); g_assert_true (ok); empty = TRUE; ok = bolt_dir_is_empty (root, &empty, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (empty); /* check that we don't mess with the dir pointer offset, * by creating a second file, iterating exactly once * and then making sure we are at the same position * after the call to bolt_dir_is_empty */ ok = bolt_write_file_at (dirfd (root), "b", "b", -1, &err); g_assert_no_error (err); g_assert_true (ok); empty = TRUE; ok = bolt_dir_is_empty (root, &empty, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (empty); while ((de = readdir (root)) != NULL) { if (!g_strcmp0 (de->d_name, ".") || !g_strcmp0 (de->d_name, "..")) continue; break; } pos = telldir (root); empty = TRUE; ok = bolt_dir_is_empty (root, &empty, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (empty); g_assert_cmpint (telldir (root), ==, pos); } static void test_autoclose (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autofree char *path = NULL; gboolean ok; { bolt_autoclose int fd = -1; fd = bolt_open ("/dev/null", O_RDONLY, 0, &err); g_assert_no_error (err); g_assert_cmpint (fd, >, -1); path = g_strdup_printf ("/proc/self/fd/%d", fd); ok = g_file_test (path, G_FILE_TEST_EXISTS); g_assert_true (ok); } ok = g_file_test (path, G_FILE_TEST_EXISTS); g_assert_false (ok); } static void test_fs (TestIO *tt, gconstpointer user_data) { g_autoptr(GFile) base = NULL; g_autoptr(GFile) dir = NULL; g_autoptr(GFile) target = NULL; g_autoptr(GFile) other = NULL; g_autoptr(GError) error = NULL; g_autofree char *path = NULL; struct stat st; gboolean ok; int r; base = g_file_new_for_path (tt->path); dir = g_file_get_child (base, "in/a/galaxy/far/far"); target = g_file_get_child (dir, "luke"); ok = bolt_fs_make_parent_dirs (target, &error); g_assert_no_error (error); g_assert_true (ok); ok = g_file_query_exists (target, NULL); g_assert_false (ok); ok = g_file_query_exists (dir, NULL); g_assert_true (ok); ok = bolt_fs_make_parent_dirs (target, &error); g_assert_no_error (error); g_assert_true (ok); ok = bolt_fs_make_parent_dirs (dir, &error); g_assert_no_error (error); g_assert_true (ok); g_clear_object (&target); target = g_file_get_child (base, "darth"); path = g_file_get_path (target); ok = g_file_set_contents (path, "vader", -1, &error); g_assert_no_error (error); g_assert_true (ok); ok = g_file_query_exists (target, NULL); g_assert_true (ok); ok = bolt_fs_make_parent_dirs (target, &error); g_assert_no_error (error); g_assert_true (ok); if (geteuid () == 0) /* if we run as root, maybe inside a container, we will * be able to do that anyway so skip it in that case */ return; /* check error checking */ r = stat (tt->path, &st); g_assert_cmpint (r, >, -1); /* make dir read only */ r = chmod (tt->path, st.st_mode & (~00222)); g_assert_cmpint (r, >, -1); other = g_file_get_child (base, "this/and/that"); ok = bolt_fs_make_parent_dirs (other, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED); g_assert_false (ok); /* and back */ r = chmod (tt->path, st.st_mode); g_assert_cmpint (r, >, -1); } #define TIME_QI "time::*" static void touch_and_compare (GFile *target, guint64 tp) { g_autoptr(GError) error = NULL; g_autoptr(GFileInfo) info = NULL; gboolean ok; guint64 ts; ok = bolt_fs_touch (target, tp, tp + 1, &error); g_assert_no_error (error); g_assert_true (ok); info = g_file_query_info (target, TIME_QI, 0, NULL, &error); g_assert_no_error (error); g_assert_nonnull (info); ts = g_file_info_get_attribute_uint64 (info, "time::modified"); g_assert_cmpuint (ts, ==, tp + 1); /* No check for access time because it might not be supported * by the underlying file system */ } static void test_fs_touch_mtime (TestIO *tt, gconstpointer user_data) { g_autoptr(GFileInfo) info = NULL; g_autoptr(GError) err = NULL; g_autoptr(GFile) base = NULL; g_autoptr(GFile) target = NULL; gboolean ok; guint64 now; guint64 tp; guint64 ts; base = g_file_new_for_path (tt->path); target = g_file_get_child (base, "this"); ok = g_file_query_exists (target, NULL); g_assert_false (ok); now = (guint64) g_get_real_time () / G_USEC_PER_SEC; touch_and_compare (target, now); tp = 626648700; touch_and_compare (target, tp); /* check mtime, omit atime */ ok = bolt_fs_touch (target, 0, 42, &err); g_assert_no_error (err); g_assert_true (ok); info = g_file_query_info (target, TIME_QI, 0, NULL, &err); g_assert_no_error (err); g_assert_nonnull (info); ts = g_file_info_get_attribute_uint64 (info, "time::modified"); g_assert_cmpuint (ts, ==, 42); /* the other way around, where 0 means now */ ok = bolt_fs_touch (target, 42, 0, &err); g_assert_no_error (err); g_assert_true (ok); g_clear_object (&info); info = g_file_query_info (target, TIME_QI, 0, NULL, &err); g_assert_no_error (err); g_assert_nonnull (info); /* mtime 0 means ignore, effectively meaning now, for touch */ ts = g_file_info_get_attribute_uint64 (info, "time::modified"); g_assert_cmpuint (ts, >=, now); now = (guint64) g_get_real_time () / G_USEC_PER_SEC; g_assert_cmpuint (ts, <=, now); } static void test_fs_touch_atime (TestIO *tt, gconstpointer user_data) { g_autoptr(GFileInfo) info = NULL; g_autoptr(GError) err = NULL; g_autoptr(GFile) base = NULL; g_autoptr(GFile) target = NULL; struct statvfs sb; gboolean ok; guint64 tp; guint64 ts; int r; /* Access time is not always supported, so support for it is best-effort. */ r = statvfs (tt->path, &sb); g_assert_cmpint (r, ==, 0); if (bolt_flag_isset (sb.f_flag, ST_NOATIME)) { g_test_skip ("file system mounted with noatime, can't modify access time"); return; } base = g_file_new_for_path (tt->path); target = g_file_get_child (base, "this"); ok = g_file_query_exists (target, NULL); g_assert_false (ok); tp = 626648700; ok = bolt_fs_touch (target, tp, 0, &err); g_assert_no_error (err); g_assert_true (ok); info = g_file_query_info (target, TIME_QI, 0, NULL, &err); g_assert_no_error (err); g_assert_nonnull (info); ts = g_file_info_get_attribute_uint64 (info, "time::access"); g_assert_cmpuint (ts, ==, tp); ok = bolt_fs_touch (target, 42, 0, &err); g_assert_no_error (err); g_assert_true (ok); g_clear_object (&info); info = g_file_query_info (target, TIME_QI, 0, NULL, &err); g_assert_no_error (err); g_assert_nonnull (info); ts = g_file_info_get_attribute_uint64 (info, "time::access"); g_assert_cmpuint (ts, ==, 42); } static void test_str (TestRng *tt, gconstpointer user_data) { GPtrArray *a = NULL; GStrv r; r = bolt_strv_from_ptr_array (NULL); g_assert_null (r); r = bolt_strv_from_ptr_array (&a); g_assert_null (r); a = g_ptr_array_new (); r = bolt_strv_from_ptr_array (&a); g_assert_nonnull (r); g_assert_null (a); g_assert_cmpuint (0U, ==, g_strv_length (r)); g_strfreev (r); a = g_ptr_array_new (); g_ptr_array_add (a, NULL); r = bolt_strv_from_ptr_array (&a); g_assert_nonnull (r); g_assert_null (a); g_assert_cmpuint (0U, ==, g_strv_length (r)); g_strfreev (r); a = g_ptr_array_new (); g_ptr_array_add (a, g_strdup ("test")); r = bolt_strv_from_ptr_array (&a); g_assert_nonnull (r); g_assert_null (a); g_assert_cmpuint (1U, ==, g_strv_length (r)); g_assert_true (g_strv_contains ((char const * const * ) r, "test")); g_strfreev (r); } static void test_str_erase (TestRng *tt, gconstpointer user_data) { g_autofree char *d1 = NULL; g_autofree char *d2 = NULL; g_autofree char *n0 = NULL; char buf[256] = {0, }; size_t n; bolt_get_random_data (buf, sizeof (buf) - 1); buf[0] = 'b'; /* make sure we never have an empty string */ buf[1] = 'o'; buf[2] = 'l'; buf[3] = 'l'; d1 = g_strdup (buf); d2 = g_strdup (buf); g_assert_nonnull (d2); g_assert_nonnull (d2); /* make sure we don't crash on NULL */ bolt_str_erase (NULL); bolt_str_erase (n0); bolt_str_erase_clear (&n0); bolt_str_erase_clear (&d2); g_assert_null (d2); n = strlen (d1); bolt_str_erase (d1); g_assert_cmpstr (d1, !=, buf); g_assert_cmpuint (strlen (d1), ==, 0); for (guint i = 0; i < n; i++) g_assert_cmpint (d1[i], ==, 0); bolt_erase_n (buf, sizeof (buf)); for (guint i = 0; i < n; i++) g_assert_cmpint (buf[i], ==, 0); } static void test_str_parse_int (TestRng *tt, gconstpointer user_data) { for (gsize i = 0; i < G_N_ELEMENTS (str_to_int_table); i++) { g_autoptr(GError) error = NULL; const char *txt = str_to_int_table[i].str; gboolean expect_error = str_to_int_table[i].error; gint val = str_to_int_table[i].val; gboolean ok; gint v; errno = 0; ok = bolt_str_parse_as_int (txt, &v, &error); if (expect_error) { int err = errno; g_assert_nonnull (error); g_assert_false (ok); g_assert_cmpint (err, !=, 0); } else { g_assert_cmpint (val, ==, v); g_assert_true (ok); } } } static void test_str_parse_uint (TestRng *tt, gconstpointer user_data) { for (gsize i = 0; i < G_N_ELEMENTS (str_to_uint_table); i++) { g_autoptr(GError) error = NULL; const char *txt = str_to_uint_table[i].str; gboolean expect_error = str_to_uint_table[i].error; guint val = str_to_uint_table[i].val; gboolean ok; guint v; errno = 0; ok = bolt_str_parse_as_uint (txt, &v, &error); if (g_test_verbose ()) g_test_message ("parsing '%s', expecting: %s", txt, (expect_error ? "error" : "success")); if (expect_error) { int err = errno; if (g_test_verbose ()) g_assert_nonnull (error); g_assert_false (ok); g_assert_cmpint (err, !=, 0); } else { g_assert_cmpuint (val, ==, v); g_assert_true (ok); } } } static void test_str_parse_uint64 (TestRng *tt, gconstpointer user_data) { struct { const char *str; guint64 val; gboolean error; } table[] = { {"0", 0, FALSE}, {"1", 1, FALSE}, {"0xffffffffffffffff", 0xffffffffffffffff, FALSE}, /* G_MAXUINT64 */ {"notauint64", 0, TRUE}, {"18446744073709551616", 0, TRUE}, /* overflow (G_MAXUINT64 + 1) */ }; for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { g_autoptr(GError) err = NULL; gboolean ok; guint64 v; ok = bolt_str_parse_as_uint64 (table[i].str, &v, &err); if (table[i].error) { int e = errno; g_assert_nonnull (err); g_assert_false (ok); g_assert_cmpint (e, !=, 0); } else { g_assert_cmpuint (table[i].val, ==, v); g_assert_true (ok); } } } static void test_str_parse_uint32 (TestRng *tt, gconstpointer user_data) { struct { const char *str; guint32 val; gboolean error; } table[] = { {"0", 0, FALSE}, {"1", 1, FALSE}, {"0xffffffff", 0xffffffff, FALSE}, /* G_MAXUINT64 */ {"notauint64", 0, TRUE}, {"4294967296", 0, TRUE}, /* overflow (G_MAXUINT32 + 1) */ }; for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { g_autoptr(GError) err = NULL; gboolean ok; guint32 v; ok = bolt_str_parse_as_uint32 (table[i].str, &v, &err); if (table[i].error) { int e = errno; g_assert_nonnull (err); g_assert_false (ok); g_assert_cmpint (e, !=, 0); } else { g_assert_cmpuint (table[i].val, ==, v); g_assert_true (ok); } } } static void test_str_parse_boolean (TestRng *tt, gconstpointer user_data) { struct { const char *str; gboolean val; gboolean error; } table[] = { {"TRUE", TRUE, FALSE}, {"YES", TRUE, FALSE}, {"1", TRUE, FALSE}, {"FALSE", FALSE, FALSE}, {"no", FALSE, FALSE}, {"0", FALSE, FALSE}, {"notabool", FALSE, TRUE}, {"12", FALSE, TRUE}, }; for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { g_autoptr(GError) err = NULL; gboolean ok; gboolean v; ok = bolt_str_parse_as_boolean (table[i].str, &v, &err); if (table[i].error) { g_assert_nonnull (err); g_assert_false (ok); } else { g_assert_cmpuint (table[i].val, ==, v); g_assert_true (ok); } } } static void test_str_set (TestRng *tt, gconstpointer user_data) { g_autofree char *target = NULL; g_autofree char *str = NULL; bolt_set_str (&target, NULL); g_assert_null (target); str = g_strdup ("test"); bolt_set_str (&target, str); g_assert_nonnull (target); g_assert_true (str == target); str = NULL; /* owned by target now */ bolt_set_strdup (&target, "foobar"); g_assert_cmpstr (target, ==, "foobar"); bolt_set_strdup_printf (&target, "%s %s", "Hallo", "Welt"); g_assert_cmpstr (target, ==, "Hallo Welt"); } static void test_strv_make_n (TestRng *tt, gconstpointer user_data) { g_auto(GStrv) empty = NULL; g_auto(GStrv) full = NULL; const char *check[] = {"voll", "voll", NULL}; empty = bolt_strv_make_n (0, "nichts"); g_assert_nonnull (empty); g_assert_null (*empty); full = bolt_strv_make_n (2, "voll"); g_assert_nonnull (full); g_assert_nonnull (*full); g_assert_cmpuint (bolt_gstrv_length0 (full), ==, 2); bolt_assert_strv_equal (full, (GStrv) check, -1); } #define MAKE_GSTRV(...) (GStrv) (const char *[]){ __VA_ARGS__} static void test_strv_length (TestRng *tt, gconstpointer user_data) { struct { const GStrv strv; gsize l; } table[] = { {NULL, 0}, {MAKE_GSTRV (NULL), 0}, {MAKE_GSTRV ("a", NULL), 1}, {MAKE_GSTRV ("a", "b", "c", "d", NULL), 4}, }; for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { g_assert_cmpuint (bolt_strv_length (table[i].strv), ==, table[i].l); g_assert_cmpuint (bolt_gstrv_length0 (table[i].strv), ==, table[i].l); if (table[i].l == 0) g_assert_true (bolt_strv_isempty (table[i].strv)); else g_assert_false (bolt_strv_isempty (table[i].strv)); } } static void test_strv_contains (TestRng *tt, gconstpointer user_data) { const GStrv strv = MAKE_GSTRV ("a", "b", "c", "d", NULL); g_assert_false (bolt_strv_contains (NULL, "nonexistent")); g_assert_false (bolt_strv_contains (strv, "nonexistent")); for (char **iter = strv; *iter; iter++) { char **target = bolt_strv_contains (strv, *iter); g_assert_nonnull (target); g_assert_cmpuint (GPOINTER_TO_UINT ((gpointer) target), ==, GPOINTER_TO_UINT ((gpointer) iter)); } } typedef struct { const GStrv a; const GStrv b; gboolean result; } StrvEqualTest; static void test_strv_equal (TestRng *tt, gconstpointer user_data) { StrvEqualTest table[] = { {NULL, NULL, TRUE}, {NULL, MAKE_GSTRV (NULL), TRUE}, {MAKE_GSTRV (NULL), NULL, TRUE}, {MAKE_GSTRV (NULL), MAKE_GSTRV (NULL), TRUE}, {MAKE_GSTRV ("a", NULL), NULL, FALSE}, {MAKE_GSTRV ("a", NULL), MAKE_GSTRV (NULL), FALSE}, {MAKE_GSTRV ("a", NULL), MAKE_GSTRV ("a", NULL), TRUE}, {MAKE_GSTRV ("a", NULL), MAKE_GSTRV ("b", NULL), FALSE}, {MAKE_GSTRV ("a", NULL), MAKE_GSTRV ("a", NULL), TRUE}, {MAKE_GSTRV ("a", "b", NULL), MAKE_GSTRV ("a", NULL), FALSE}, {MAKE_GSTRV ("a", "b", NULL), MAKE_GSTRV ("a", "b", NULL), TRUE}, {MAKE_GSTRV ("a", "a", NULL), MAKE_GSTRV ("a", "b", NULL), FALSE}, {MAKE_GSTRV ("a", "a", NULL), MAKE_GSTRV ("a", "b", NULL), FALSE}, }; for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { StrvEqualTest *t = &table[i]; gboolean res = bolt_strv_equal (t->a, t->b); g_debug ("strv-equal[%2" G_GSIZE_FORMAT "] expected | got: %3s | %3s", i, bolt_yesno (res), bolt_yesno (res)); g_assert_true (res == t->result); } } typedef struct { const GStrv before; const GStrv after; gboolean result; const GStrv added; const GStrv removed; } StrvDiffTest; #define MK_STRV0(...) (GStrv) (const char *[]){ __VA_ARGS__, NULL} static void test_strv_diff (TestRng *tt, gconstpointer user_data) { StrvDiffTest table[] = { {NULL, NULL, FALSE, NULL, NULL }, {MK_STRV0 ("a"), MK_STRV0 ("a"), FALSE, NULL, NULL }, {MK_STRV0 ("a", "b"), MK_STRV0 ("a", "b"), FALSE, NULL, NULL }, {NULL, MK_STRV0 ("a"), TRUE, MK_STRV0 ("a"), NULL }, {NULL, MK_STRV0 ("a", "b"), TRUE, MK_STRV0 ("a", "b"), NULL }, {MK_STRV0 ("a"), NULL, TRUE, NULL, MK_STRV0 ("a") }, {MK_STRV0 ("a", "b"), NULL, TRUE, NULL, MK_STRV0 ("a", "b")}, {MK_STRV0 ("a", "b", "d"), MK_STRV0 ("a", "c", "d"), TRUE, MK_STRV0 ("c"), MK_STRV0 ("b") }, {MK_STRV0 ("a", "b", "x"), MK_STRV0 ("x", "c", "d"), TRUE, MK_STRV0 ("c", "d"), MK_STRV0 ("a", "b")}, {MK_STRV0 ("b", "x", "a"), MK_STRV0 ("d", "x", "c"), TRUE, MK_STRV0 ("c", "d"), MK_STRV0 ("a", "b")}, }; for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { g_autoptr(GHashTable) diff = NULL; g_autoptr(GPtrArray) add = NULL; g_autoptr(GPtrArray) del = NULL; GStrv added = NULL; GStrv removed = NULL; StrvDiffTest *t = &table[i]; GHashTableIter hi; gpointer k, v; gboolean add_equal; gboolean rem_equal; gboolean res; diff = bolt_strv_diff (t->before, t->after); add = g_ptr_array_new (); del = g_ptr_array_new (); g_hash_table_iter_init (&hi, diff); while (g_hash_table_iter_next (&hi, &k, &v)) { gint op = GPOINTER_TO_INT (v); if (op == '+') g_ptr_array_add (add, k); else if (op == '-') g_ptr_array_add (del, k); else g_warning ("unknown op: %c", op); } g_ptr_array_sort (add, bolt_comparefn_strcmp); g_ptr_array_sort (del, bolt_comparefn_strcmp); g_ptr_array_add (add, NULL); g_ptr_array_add (del, NULL); added = (GStrv) add->pdata; removed = (GStrv) del->pdata; res = g_hash_table_size (diff) > 0; add_equal = bolt_strv_equal (added, t->added); rem_equal = bolt_strv_equal (removed, t->removed); g_debug ("strv-diff[%2" G_GSIZE_FORMAT "] expected, got | %3s, %3s add: %s, rem: %s", i, bolt_yesno (t->result), bolt_yesno (res), bolt_yesno (add_equal), bolt_yesno (rem_equal)); g_assert_true (res == t->result); bolt_assert_strv_equal (added, t->added, -1); bolt_assert_strv_equal (removed, t->removed, -1); } } static void test_strv_permute (TestRng *tt, gconstpointer user_data) { g_auto(GStrv) tst = NULL; const char *ref[] = {"a", "b", "c", "d", NULL}; char *empty[] = {NULL}; guint N; guint k = 0; bolt_strv_permute (NULL); bolt_strv_permute (empty); g_assert_cmpuint (bolt_strv_length (empty), ==, 0U); tst = g_strdupv ((char **) ref); /* there are 4! = 24 possible permutations, do it * at least N = 4! and pick a rather large threshold * instead of a larger N */ N = (4 * 3 * 2 * 1); for (guint i = 0; i < N; i++) { bolt_strv_permute (tst); if (bolt_strv_equal ((char **) ref, (char **) tst)) k++; } g_debug ("permutation-test: %u of %u were equal", k, N); g_assert_cmpuint (k, <, 5); } static void test_strv_rotate_left (TestRng *tt, gconstpointer user_data) { g_auto(GStrv) a = NULL; char **target; /* handle NULL */ target = bolt_strv_rotate_left (NULL); g_assert_null (target); /* single element */ a = g_strsplit ("a", ":", -1); g_assert_cmpuint (g_strv_length (a), ==, 1); g_assert_cmpstr (a[0], ==, "a"); target = bolt_strv_rotate_left (a); g_assert_cmpuint (g_strv_length (a), ==, 1); g_assert_cmpstr (a[0], ==, "a"); g_assert_true (target == &a[0]); /* two elements */ g_strfreev (a); a = g_strsplit ("a:b", ":", -1); g_assert_cmpuint (g_strv_length (a), ==, 2); g_assert_cmpstr (a[0], ==, "a"); g_assert_cmpstr (a[1], ==, "b"); target = bolt_strv_rotate_left (a); g_assert_cmpuint (g_strv_length (a), ==, 2); g_assert_cmpstr (a[1], ==, "a"); g_assert_cmpstr (a[0], ==, "b"); g_assert_true (target == &a[1]); /* now > 2 */ g_strfreev (a); a = g_strsplit ("a:b:c:d:e", ":", -1); g_assert_cmpuint (g_strv_length (a), ==, 5); g_assert_cmpstr (a[0], ==, "a"); g_assert_cmpstr (a[4], ==, "e"); target = bolt_strv_rotate_left (a); g_assert_cmpuint (g_strv_length (a), ==, 5); g_assert_cmpstr (a[0], ==, "b"); g_assert_cmpstr (a[1], ==, "c"); g_assert_cmpstr (a[2], ==, "d"); g_assert_cmpstr (a[3], ==, "e"); g_assert_cmpstr (a[4], ==, "a"); g_assert_true (target == &a[4]); /* now with empty strings in between */ g_strfreev (a); a = g_strsplit ("a:::d:e", ":", -1); g_assert_cmpuint (g_strv_length (a), ==, 5); g_assert_cmpstr (a[0], ==, "a"); g_assert_cmpstr (a[3], ==, "d"); g_assert_cmpstr (a[4], ==, "e"); target = bolt_strv_rotate_left (a); g_assert_cmpstr (a[0], ==, ""); g_assert_cmpstr (a[1], ==, ""); g_assert_cmpstr (a[2], ==, "d"); g_assert_cmpstr (a[3], ==, "e"); g_assert_cmpstr (a[4], ==, "a"); g_assert_true (target == &a[4]); } #ifndef MAKE_GSTRV #define MAKE_GSTRV(...) (GStrv) (const char *[]){ __VA_ARGS__} #endif static void test_uuidv_check (TestRng *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; char *empty[] = {NULL}; const char *empty_entries[] = {"884c6edd-7118-4b21-b186-b02d396ecca0", "", NULL}; const char *valid[] = {"884c6edd-7118-4b21-b186-b02d396ecca0", NULL}; const GStrv invalid[] = { MAKE_GSTRV ("\n", NULL), MAKE_GSTRV ("884c6eddx7118x4b21xb186-b02d396ecca0", NULL), MAKE_GSTRV ("884c6edd-4b21-b186-b02d396ecca0", NULL), MAKE_GSTRV ("884c6edd-7118-4b21-b186-b02d396ecca0", "a", NULL), }; gboolean ok; ok = bolt_uuidv_check (NULL, TRUE, &err); g_assert_true (ok); ok = bolt_uuidv_check (empty, TRUE, &err); g_assert_true (ok); ok = bolt_uuidv_check ((GStrv) empty_entries, TRUE, &err); g_assert_true (ok); ok = bolt_uuidv_check (NULL, FALSE, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_error (&err); ok = bolt_uuidv_check (empty, FALSE, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_error (&err); ok = bolt_uuidv_check ((GStrv) empty_entries, FALSE, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_error (&err); ok = bolt_uuidv_check ((GStrv) valid, TRUE, &err); g_assert_true (ok); for (gsize i = 0; i < G_N_ELEMENTS (invalid); i++) { ok = bolt_uuidv_check (invalid[i], FALSE, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_error (&err); } } static void test_term_fancy (TestRng *tt, gconstpointer user_data) { skip_test_unless (bolt_is_fancy_terminal (), "Terminal is not fancy"); g_assert_cmpstr (bolt_color (ANSI_NORMAL), !=, ""); g_assert_cmpstr (bolt_glyph (WARNING_SIGN), !=, ""); } static void test_term_plain (TestRng *tt, gconstpointer user_data) { skip_test_if (bolt_is_fancy_terminal (), "Terminal is too fancy"); g_assert_cmpstr (bolt_color (ANSI_NORMAL), ==, ""); g_assert_cmpstr (bolt_glyph (WARNING_SIGN), !=, ""); } static void test_time (TestRng *tt, gconstpointer user_data) { g_autofree char *str = NULL; str = bolt_epoch_format (0, "%Y"); g_assert_cmpstr (str, ==, "1970"); } static void test_list_nh (TestRng *tt, gconstpointer user_data) { BoltList n[10]; BoltList *l = n; BoltList *k; BoltList iter; guint c; bolt_list_init (l); g_assert_cmpuint (bolt_nhlist_len (NULL), ==, 0); g_assert_cmpuint (bolt_nhlist_len (l), ==, 1); g_assert_true (l->next == l); g_assert_true (l->prev == l); c = 0; bolt_nhlist_iter_init (&iter, l); while ((k = bolt_nhlist_iter_next (&iter))) { BoltList *p = bolt_nhlist_iter_node (&iter); g_assert_true (k == l); g_assert_true (p == l); c++; } g_assert_cmpuint (c, ==, 1); for (gsize i = 1; i < 10; i++) { bolt_list_init (&n[i]); bolt_list_add_before (l, &n[i]); g_assert_cmpuint (bolt_nhlist_len (l), ==, i + 1); } for (gsize i = 0; i < 10; i++) { gsize j = (i + 1) % 10; g_assert_true (l[i].next == &l[j]); g_assert_true (l[j].prev == &l[i]); g_assert_true (l[i].next->prev == &l[i]); g_assert_true (l[i].prev->next == &l[i]); } /* start in the middle */ for (guint i = 0; i < 10; i++) { c = 0; bolt_nhlist_iter_init (&iter, &n[i]); while ((k = bolt_nhlist_iter_next (&iter))) { BoltList *p = bolt_nhlist_iter_node (&iter); g_assert_true (k == n + ((c + i) % 10)); g_assert_true (k == p); c++; } g_assert_cmpuint (c, ==, 10); g_debug ("start[%u] %p: count: %u", i, &n[i], c); } } static void test_steal (TestRng *tt, gconstpointer user_data) { unsigned int arr[] = {0, 1, 2}; unsigned int uit; char c = ' '; char *ptr = &c; char *ref; int ifd = 42; int chk; uit = bolt_steal (arr + 1, 0); g_assert_cmpuint (uit, ==, 1); g_assert_cmpuint (arr[1], ==, 0); ref = bolt_steal (&ptr, NULL); g_assert_true (ptr == NULL); g_assert_true (ref == &c); chk = bolt_steal (&ifd, -1); g_assert_cmpint (chk, ==, 42); g_assert_cmpint (ifd, ==, -1); } static void test_swap (TestRng *tt, gconstpointer user_data) { int ia = 0, ib = 1; int *pa = &ia, *pb = &ib; g_assert_cmpint (ia, ==, 0); g_assert_cmpint (ib, ==, 1); bolt_swap (ia, ib); g_assert_cmpint (ia, ==, 1); g_assert_cmpint (ib, ==, 0); bolt_swap (pa, pb); g_assert_true (pa == &ib); g_assert_true (pb == &ia); bolt_swap (pa, pa); g_assert_true (pa == &ib); g_assert_cmpint (*pa, ==, ib); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_resources_register (bolt_test_get_resource ()); g_test_add ("/common/dbus/interface_info_find", TestDummy, NULL, NULL, test_dbus_interface_info_find, NULL); g_test_add ("/common/dbus/interface_info_lookup", TestDummy, NULL, NULL, test_dbus_interface_info_lookup, NULL); g_test_add ("/common/enums", TestRng, NULL, NULL, test_enums, NULL); g_test_add ("/common/error", TestRng, NULL, NULL, test_error, NULL); g_test_add ("/common/flags", TestRng, NULL, NULL, test_flags, NULL); g_test_add ("/common/rng", TestRng, NULL, NULL, test_rng, NULL); g_test_add ("/common/io/errors", TestIO, NULL, test_io_setup, test_io_errors, test_io_tear_down); g_test_add ("/common/io/mkdirat", TestIO, NULL, test_io_setup, test_io_mkdirat, test_io_tear_down); g_test_add ("/common/io/verify", TestIO, NULL, test_io_setup, test_io_verify, test_io_tear_down); g_test_add ("/common/io/write_file_at", TestIO, NULL, test_io_setup, test_io_write_file_at, test_io_tear_down); g_test_add ("/common/io/write_int_at", TestIO, NULL, test_io_setup, test_io_write_int_at, test_io_tear_down); g_test_add ("/common/io/read_int_at", TestIO, NULL, test_io_setup, test_io_read_int_at, test_io_tear_down); g_test_add ("/common/io/write_uint_at", TestIO, NULL, test_io_setup, test_io_write_uint_at, test_io_tear_down); g_test_add ("/common/io/read_uint_at", TestIO, NULL, test_io_setup, test_io_read_uint_at, test_io_tear_down); g_test_add ("/common/io/file_write_all", TestIO, NULL, test_io_setup, test_io_file_write_all, test_io_tear_down); g_test_add ("/common/io/renameat", TestIO, NULL, test_io_setup, test_io_renameat, test_io_tear_down); g_test_add ("/common/io/copy_bytes", TestIO, NULL, test_io_setup, test_io_copy_bytes, test_io_tear_down); g_test_add ("/common/io/dir_is_empty", TestIO, NULL, test_io_setup, test_io_dir_is_empty, test_io_tear_down); g_test_add ("/common/io/autoclose", TestIO, NULL, NULL, test_autoclose, NULL); g_test_add ("/common/fs", TestIO, NULL, test_io_setup, test_fs, test_io_tear_down); g_test_add ("/common/fs/touch/mtime", TestIO, NULL, test_io_setup, test_fs_touch_mtime, test_io_tear_down); g_test_add ("/common/fs/touch/atime", TestIO, NULL, test_io_setup, test_fs_touch_atime, test_io_tear_down); g_test_add ("/common/str", TestRng, NULL, NULL, test_str, NULL); g_test_add ("/common/str/erase", TestRng, NULL, NULL, test_str_erase, NULL); g_test_add ("/common/str/parse/int", TestRng, NULL, NULL, test_str_parse_int, NULL); g_test_add ("/common/str/parse/uint", TestRng, NULL, NULL, test_str_parse_uint, NULL); g_test_add ("/common/str/parse/uint64", TestRng, NULL, NULL, test_str_parse_uint64, NULL); g_test_add ("/common/str/parse/uint32", TestRng, NULL, NULL, test_str_parse_uint32, NULL); g_test_add ("/common/str/parse/boolean", TestRng, NULL, NULL, test_str_parse_boolean, NULL); g_test_add ("/common/str/set", TestRng, NULL, NULL, test_str_set, NULL); g_test_add ("/common/strv/make_n", TestRng, NULL, NULL, test_strv_make_n, NULL); g_test_add ("/common/strv/equal", TestRng, NULL, NULL, test_strv_equal, NULL); g_test_add ("/common/strv/length", TestRng, NULL, NULL, test_strv_length, NULL); g_test_add ("/common/strv/contains", TestRng, NULL, NULL, test_strv_contains, NULL); g_test_add ("/common/strv/diff", TestRng, NULL, NULL, test_strv_diff, NULL); g_test_add ("/common/strv/permute", TestRng, NULL, NULL, test_strv_permute, NULL); g_test_add ("/common/strv/rotate_left", TestRng, NULL, NULL, test_strv_rotate_left, NULL); g_test_add ("/common/uuidv/check", TestRng, NULL, NULL, test_uuidv_check, NULL); g_test_add ("/common/term/fancy", TestRng, NULL, NULL, test_term_fancy, NULL); g_test_add ("/common/term/plain", TestRng, NULL, NULL, test_term_plain, NULL); g_test_add ("/common/time", TestRng, NULL, NULL, test_time, NULL); g_test_add ("/common/list/nh", TestRng, NULL, NULL, test_list_nh, NULL); g_test_add ("/common/macro/steal", TestRng, NULL, NULL, test_steal, NULL); g_test_add ("/common/macro/swap", TestRng, NULL, NULL, test_swap, NULL); return g_test_run (); } bolt-0.9.2/tests/test-device.c000066400000000000000000000055111417453051000162060ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-device.h" #include "bolt-dbus.h" #include "bolt-domain.h" #include "bolt-store.h" #include "bolt-error.h" #include typedef struct { int dummy; } TestDevice; static void test_device_basic (TestDevice *tt, gconstpointer user_data) { g_autoptr(BoltDevice) dev = NULL; g_autoptr(BoltDomain) dom = NULL; g_autoptr(BoltStore) store = NULL; g_autoptr(BoltKey) key = NULL; g_autoptr(GError) err = NULL; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; BoltDeviceType devtype = BOLT_DEVICE_PERIPHERAL; BoltKeyState keystate; BoltSecurity sl; guint gen; gboolean ok; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "type", BOLT_DEVICE_HOST, "status", BOLT_STATUS_DISCONNECTED, "generation", 3, NULL); g_assert_nonnull (dev); g_object_get (dev, "store", &store, "domain", &dom, "security", &sl, "generation", &gen, "type", &devtype, NULL); g_assert_null (store); g_assert_null (dom); g_assert_true (dom == bolt_device_get_domain (dev)); g_assert_cmpint (sl, ==, BOLT_SECURITY_UNKNOWN); g_assert_cmpint (gen, ==, 3); g_assert_cmpint (gen, ==, bolt_device_get_generation (dev)); g_assert_cmpint (devtype, ==, BOLT_DEVICE_HOST); g_assert_true (bolt_device_is_host (dev)); keystate = bolt_device_get_keystate (dev); g_assert_cmpint (keystate, ==, BOLT_KEY_MISSING); ok = bolt_device_get_key_from_sysfs (dev, &key, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_BADSTATE); g_assert_false (ok); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/device/basic", TestDevice, NULL, NULL, test_device_basic, NULL); return g_test_run (); } bolt-0.9.2/tests/test-enums.h000066400000000000000000000031161417453051000161020ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #pragma once #include "test-enum-types.h" /** * BoltTestEnum * @BOLT_TEST_UNKNOWN: Unknown * @BOLT_TEST_ONE: One * @BOLT_TEST_TOW: Two * @BOLT_TEST_THRE: Three * * Test enumeration. */ typedef enum { BOLT_TEST_UNKNOWN = -1, BOLT_TEST_ONE, BOLT_TEST_TWO, BOLT_TEST_THREE } BoltTestEnum; /** * BoltTestFlags: * @BOLT_KITT_DISABLED: Flag for everthying is disabled. * @BOLT_KITT_ENABLED: Enabled flag. * @BOLT_KITT_SSPM: Super Pursuit Mode. * @BOLT_KITT_TURBO_BOOST: Turbo Boost. * @BOLT_KITT_SKI_MODE: Ski Mode. * * KITT features. */ typedef enum { /*< flags >*/ BOLT_KITT_DISABLED = 0, BOLT_KITT_ENABLED = 1, BOLT_KITT_SSPM = 1 << 1, BOLT_KITT_TURBO_BOOST = 1 << 2, BOLT_KITT_SKI_MODE = 1 << 3, BOLT_KITT_DEFAULT = BOLT_KITT_ENABLED | BOLT_KITT_SSPM, } BoltKittFlags; bolt-0.9.2/tests/test-exported.c000066400000000000000000001176441417453051000166140ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-exported.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-glue.h" #include "bolt-str.h" #include "test-enums.h" #include "bolt-test-resources.h" #include #include #include #include /* *** Tiny object with only an "id" property */ #define BT_TYPE_ID bt_id_get_type () G_DECLARE_FINAL_TYPE (BtId, bt_id, BT, ID, BoltExported); struct _BtId { BoltExported parent; }; G_DEFINE_TYPE (BtId, bt_id, BOLT_TYPE_EXPORTED); enum { PROP_ID_0, PROP_OBJECT_ID, PROP_ID, PROP_ID_LAST }; static GParamSpec *id_props[PROP_ID_LAST] = {NULL, }; static void bt_id_init (BtId *be) { } static void bt_id_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_ID: case PROP_OBJECT_ID: g_value_set_string (value, "bolt-id"); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bt_id_class_init (BtIdClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bt_id_get_property; id_props[PROP_OBJECT_ID] = bolt_param_spec_override (gobject_class, "object-id"); id_props[PROP_ID] = g_param_spec_string ("id", "Id", NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); g_object_class_install_properties (gobject_class, PROP_ID_LAST, id_props); } /* */ #define DBUS_IFACE "org.gnome.bolt.Example" #define DBUS_OPATH_BASE "/bolt/test" #define BT_TYPE_EXPORTED bt_exported_get_type () G_DECLARE_FINAL_TYPE (BtExported, bt_exported, BT, EXPORTED, BoltExported); static GVariant * handle_ping (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error); static GVariant * handle_peng (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error); static gboolean handle_authorize_method (BoltExported *exported, GDBusMethodInvocation *inv, GError **error, gpointer user_data); static gboolean handle_authorize_property (BoltExported *exported, const char *name, gboolean setting, GDBusMethodInvocation *invocation, GError **error, gpointer user_data); static gboolean handle_set_str_rw (BoltExported *obj, const char *name, const GValue *value, GError **error); static gboolean handle_set_security (BoltExported *obj, const char *name, const GValue *value, GError **error); static gboolean handle_set_kitt (BoltExported *obj, const char *name, const GValue *value, GError **error); struct _BtExported { BoltExported parent; char *object_id; char *str; GError *setter_err; gboolean prop_bool; BtId *prop_obj; gboolean authorize_methods; gboolean authorize_properties; BoltSecurity security; BoltKittFlags kitt; }; G_DEFINE_TYPE (BtExported, bt_exported, BOLT_TYPE_EXPORTED); enum { PROP_0, PROP_MYID, PROP_STR, PROP_STR_RW, PROP_STR_RW_NOSETTER, PROP_BOOL, PROP_OBJECT, PROP_SECURITY, PROP_KITT, PROP_LAST }; static GParamSpec *props[PROP_LAST] = {NULL, }; static void bt_exported_finalize (GObject *object) { BtExported *be = BT_EXPORTED (object); g_clear_object (&be->prop_obj); g_free (be->str); g_free (be->object_id); G_OBJECT_CLASS (bt_exported_parent_class)->finalize (object); } static void bt_exported_init (BtExported *be) { be->object_id = g_strdup ("bt_exported0"); be->str = g_strdup ("strfoo"); be->prop_obj = g_object_new (BT_TYPE_ID, NULL); } static void bt_exported_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BtExported *be = BT_EXPORTED (object); switch (prop_id) { case PROP_MYID: g_value_set_string (value, be->object_id); break; case PROP_STR: case PROP_STR_RW: case PROP_STR_RW_NOSETTER: g_value_set_string (value, be->str); break; case PROP_BOOL: g_value_set_boolean (value, be->prop_bool); break; case PROP_OBJECT: g_value_set_object (value, be->prop_obj); break; case PROP_SECURITY: g_value_set_enum (value, be->security); break; case PROP_KITT: g_value_set_flags (value, be->kitt); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bt_exported_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BtExported *be = BT_EXPORTED (object); switch (prop_id) { case PROP_STR: case PROP_STR_RW: case PROP_STR_RW_NOSETTER: g_clear_pointer (&be->str, g_free); be->str = g_value_dup_string (value); break; case PROP_BOOL: be->prop_bool = g_value_get_boolean (value); break; case PROP_SECURITY: be->security = g_value_get_enum (value); break; case PROP_KITT: be->kitt = g_value_get_flags (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bt_exported_class_init (BtExportedClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); BoltExportedClass *exported_class = BOLT_EXPORTED_CLASS (klass); gobject_class->finalize = bt_exported_finalize; gobject_class->get_property = bt_exported_get_property; gobject_class->set_property = bt_exported_set_property; bolt_exported_class_set_interface_info (exported_class, DBUS_IFACE, "/bolt/tests/exported/example.bolt.xml"); bolt_exported_class_set_object_path (exported_class, DBUS_OPATH_BASE); props[PROP_MYID] = bolt_param_spec_override (gobject_class, "object-id"); props[PROP_STR] = g_param_spec_string ("str", "StrFoo", NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); props[PROP_STR_RW] = g_param_spec_string ("str-rw", "StrRW", NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); props[PROP_STR_RW_NOSETTER] = g_param_spec_string ("str-rw-nosetter", "StrRWNoSetter", NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); props[PROP_BOOL] = g_param_spec_boolean ("bool", "Bool", NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); props[PROP_OBJECT] = g_param_spec_object ("object", "Object", NULL, BT_TYPE_ID, G_PARAM_READABLE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); props[PROP_SECURITY] = g_param_spec_enum ("security", "Security", NULL, BOLT_TYPE_SECURITY, BOLT_SECURITY_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); props[PROP_KITT] = g_param_spec_flags ("kitt", "KittMode", NULL, BOLT_TYPE_KITT_FLAGS, BOLT_KITT_DISABLED, G_PARAM_READWRITE | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); g_object_class_install_properties (gobject_class, PROP_LAST, props); bolt_exported_class_export_properties (exported_class, PROP_STR, PROP_LAST, props); bolt_exported_class_export_method (exported_class, "Ping", handle_ping); bolt_exported_class_export_method (exported_class, "Peng", handle_peng); bolt_exported_class_property_setter (exported_class, props[PROP_STR_RW], handle_set_str_rw); bolt_exported_class_property_setter (exported_class, props[PROP_SECURITY], handle_set_security); bolt_exported_class_property_setter (exported_class, props[PROP_KITT], handle_set_kitt); } static gboolean handle_authorize_method (BoltExported *exported, GDBusMethodInvocation *inv, GError **error, gpointer user_data) { BtExported *be = BT_EXPORTED (user_data); gboolean authorize = be->authorize_methods; const char *name = g_dbus_method_invocation_get_method_name (inv); g_debug ("authorizing method %s (%s)", name, authorize ? "y" : "n" ); if (!authorize) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "denying property write access for %s", name); return authorize; } static gboolean handle_authorize_property (BoltExported *exported, const char *name, gboolean setting, GDBusMethodInvocation *inv, GError **error, gpointer user_data) { BtExported *be = BT_EXPORTED (user_data); gboolean authorize = be->authorize_properties; g_debug ("authorizing property %s (%s)", name, authorize ? "y" : "n" ); if (!authorize) g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "denying property write access for %s", name); return authorize; } static void bt_exported_install_method_authorizer (BtExported *be) { g_signal_connect (be, "authorize-method", G_CALLBACK (handle_authorize_method), be); } static void bt_exported_install_property_authorizer (BtExported *be) { g_signal_connect (be, "authorize-property", G_CALLBACK (handle_authorize_property), be); } static GVariant * handle_ping (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error) { // BtExported *be = BT_EXPORTED (obj); return g_variant_new ("(s)", "PONG"); } static GVariant * handle_peng (BoltExported *obj, GVariant *params, GDBusMethodInvocation *inv, GError **error) { const char *str; g_variant_get_child (params, 0, "&s", &str); if (str == NULL) str = "PENG"; g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "failing with: %s", str); return NULL; } static gboolean handle_set_str_rw (BoltExported *obj, const char *name, const GValue *value, GError **error) { BtExported *be = BT_EXPORTED (obj); g_printerr ("handling set str-rw\n"); if (be->setter_err) { g_printerr ("signaling error\n"); //*error = g_error_copy (be->setter_err); g_set_error_literal (error, be->setter_err->domain, be->setter_err->code, be->setter_err->message); return FALSE; } g_clear_pointer (&be->str, g_free); be->str = g_value_dup_string (value); return TRUE; } static gboolean handle_set_security (BoltExported *obj, const char *name, const GValue *value, GError **error) { BtExported *be = BT_EXPORTED (obj); be->security = g_value_get_enum (value); return TRUE; } static gboolean handle_set_kitt (BoltExported *obj, const char *name, const GValue *value, GError **error) { BtExported *be = BT_EXPORTED (obj); be->kitt = g_value_get_flags (value); return TRUE; } /* *********** */ static GTestDBus *test_bus; typedef struct { GDBusConnection *bus; BtExported *obj; const char *bus_name; const char *obj_path; } TestExported; static void test_exported_setup (TestExported *tt, gconstpointer data) { g_autoptr(GError) err = NULL; g_autofree char *opath = NULL; const char *obj_path = "/obj"; gboolean exported = FALSE; gboolean ok; tt->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err); g_assert_no_error (err); g_assert_nonnull (tt->bus); g_assert_false (g_dbus_connection_is_closed (tt->bus)); tt->obj = g_object_new (BT_TYPE_EXPORTED, NULL); g_assert_nonnull (tt->obj); ok = bolt_exported_export (BOLT_EXPORTED (tt->obj), tt->bus, obj_path, &err); g_assert_no_error (err); g_assert_true (ok); tt->obj_path = bolt_exported_get_object_path (BOLT_EXPORTED (tt->obj)); g_assert_cmpstr (tt->obj_path, ==, obj_path); tt->bus_name = g_dbus_connection_get_unique_name (tt->bus); g_object_get (tt->obj, "object-path", &opath, "exported", &exported, NULL); g_assert_cmpstr (obj_path, ==, opath); g_assert_true (exported); g_assert_true (bolt_exported_is_exported (BOLT_EXPORTED (tt->obj))); } static void test_exported_teardown (TestExported *tt, gconstpointer data) { gboolean ok; ok = bolt_exported_unexport (BOLT_EXPORTED (tt->obj)); g_assert_true (ok); g_assert_false (bolt_exported_is_exported (BOLT_EXPORTED (tt->obj))); g_clear_object (&tt->bus); g_clear_object (&tt->obj); } typedef struct CallCtx { GMainLoop *loop; GVariant *data; GError *error; } CallCtx; static CallCtx * call_ctx_new (void) { CallCtx *ctx = g_new0 (CallCtx, 1); ctx->loop = g_main_loop_new (NULL, FALSE); return ctx; } static void call_ctx_reset (CallCtx *ctx) { if (ctx->data) g_variant_unref (ctx->data); g_clear_error (&ctx->error); } static void call_ctx_free (CallCtx *ctx) { g_return_if_fail (ctx != NULL); g_main_loop_unref (ctx->loop); call_ctx_reset (ctx); g_free (ctx); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (CallCtx, call_ctx_free); static void dbus_call_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { CallCtx *ctx = user_data; GDBusConnection *bus = G_DBUS_CONNECTION (source_object); ctx->data = g_dbus_connection_call_finish (bus, res, &ctx->error); g_main_loop_quit (ctx->loop); } static void call_ctx_run (CallCtx *ctx) { call_ctx_reset (ctx); g_main_loop_run (ctx->loop); } /* the actual tests */ static void test_exported_export (TestExported *unused, gconstpointer data) { g_autoptr(GError) err = NULL; g_autoptr(GDBusConnection) bus = NULL; g_autoptr(BtExported) obj = NULL; g_autofree char *want = NULL; g_autofree char *have = NULL; const char *obj_path = NULL; gboolean exported = FALSE; gboolean ok; bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err); g_assert_no_error (err); g_assert_nonnull (bus); g_assert_false (g_dbus_connection_is_closed (bus)); obj = g_object_new (BT_TYPE_EXPORTED, NULL); g_assert_nonnull (obj); /* we test auto object path generation */ want = g_build_path ("/", DBUS_OPATH_BASE, obj->object_id, NULL); ok = bolt_exported_export (BOLT_EXPORTED (obj), bus, NULL, &err); g_assert_no_error (err); g_assert_true (ok); obj_path = bolt_exported_get_object_path (BOLT_EXPORTED (obj)); g_assert_cmpstr (want, ==, obj_path); g_object_get (obj, "object-path", &have, "exported", &exported, NULL); g_assert_cmpstr (want, ==, have); g_assert_true (exported); g_assert_true (bolt_exported_is_exported (BOLT_EXPORTED (obj))); /* unexport */ ok = bolt_exported_unexport (BOLT_EXPORTED (obj)); g_assert_true (ok); g_assert_false (bolt_exported_is_exported (BOLT_EXPORTED (obj))); /* test we handle special chars in object id */ bolt_set_strdup (&obj->object_id, "object id-@$1"); ok = bolt_exported_export (BOLT_EXPORTED (obj), bus, NULL, &err); g_assert_no_error (err); g_assert_true (ok); obj_path = bolt_exported_get_object_path (BOLT_EXPORTED (obj)); bolt_set_str (&want, g_build_path ("/", DBUS_OPATH_BASE, "object_id___1", NULL)); g_assert_cmpstr (want, ==, obj_path); } static void test_exported_basic (TestExported *tt, gconstpointer data) { g_autoptr(GError) error = NULL; g_autoptr(CallCtx) ctx = NULL; GDBusConnection *bus; gboolean ok; const char *str = NULL; ctx = call_ctx_new (); bus = tt->bus; ok = bolt_exported_export (BOLT_EXPORTED (tt->obj), tt->bus, tt->obj_path, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); g_assert_false (ok); g_clear_error (&error); /* unknown method */ g_dbus_connection_call (bus, tt->bus_name, tt->obj_path, DBUS_IFACE, "UnknownMethodFooBarSee", NULL, G_VARIANT_TYPE ("(s)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD); /* authorization missing */ g_dbus_connection_call (bus, tt->bus_name, tt->obj_path, DBUS_IFACE, "Ping", NULL, G_VARIANT_TYPE ("(s)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED); bt_exported_install_method_authorizer (tt->obj); tt->obj->authorize_methods = TRUE; g_dbus_connection_call (bus, tt->bus_name, tt->obj_path, DBUS_IFACE, "Ping", NULL, G_VARIANT_TYPE ("(s)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_nonnull (ctx->data); g_variant_get (ctx->data, "(&s)", &str); g_assert_cmpstr (str, ==, "PONG"); /* check error handling in BoltExported's method dispatching */ g_dbus_connection_call (bus, tt->bus_name, tt->obj_path, DBUS_IFACE, "Peng", g_variant_new ("(s)", "Out of cheese"), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, BOLT_ERROR, BOLT_ERROR_FAILED); } static void test_exported_props (TestExported *tt, gconstpointer data) { g_autoptr(CallCtx) ctx = NULL; g_autoptr(GVariant) v = NULL; const char *str; ctx = call_ctx_new (); /* unknown property */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", DBUS_IFACE, "UnknownProperty"), G_VARIANT_TYPE ("(v)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); /* */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", DBUS_IFACE, "StrFoo"), G_VARIANT_TYPE ("(v)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_nonnull (ctx->data); g_variant_get (ctx->data, "(v)", &v); str = g_variant_get_string (v, NULL); g_assert_cmpstr (str, ==, tt->obj->str); /* property setter - read only property */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "StrFoo", g_variant_new ("s", "se")), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); /* property setter - no setter */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "StrRWNoSetter", g_variant_new ("s", "se")), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); /* property setter - not authorized */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "StrRW", g_variant_new ("s", "se")), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED); /* install the auth handler, be reject the auth request in it */ bt_exported_install_property_authorizer (tt->obj); tt->obj->authorize_properties = FALSE; g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "StrRW", g_variant_new ("s", "se")), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED); /* property setter - allow it, but signal an error during property setting */ tt->obj->authorize_properties = TRUE; g_set_error (&tt->obj->setter_err, BOLT_ERROR, BOLT_ERROR_CFG, "failed"); g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "StrRW", g_variant_new ("s", "se")), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, BOLT_ERROR, BOLT_ERROR_CFG); g_clear_error (&tt->obj->setter_err); /* property setter - allow it, should work now */ tt->obj->authorize_properties = TRUE; str = "new property value"; g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "StrRW", g_variant_new ("s", str)), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_cmpstr (str, ==, tt->obj->str); } static void props_changed_signal (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { CallCtx *ctx = user_data; const gchar *interface_name_for_signal; GVariant *changed_properties; g_auto(GStrv) invalidated_properties = NULL; g_variant_get (parameters, "(&s@a{sv}^a&s)", &interface_name_for_signal, &changed_properties, &invalidated_properties); g_debug ("got prop changes signal"); ctx->data = changed_properties; /* transfer ownership */ if (g_main_loop_is_running (ctx->loop)) g_main_loop_quit (ctx->loop); } static gboolean change_properties (gpointer user_data) { TestExported *tt = user_data; g_object_set (tt->obj, "str-rw", "huhu", "bool", TRUE, NULL); return G_SOURCE_REMOVE; } static void test_exported_props_changed (TestExported *tt, gconstpointer data) { g_autoptr(CallCtx) ctx = NULL; g_autoptr(GVariantIter) iter = NULL; gboolean have_bool = FALSE; gboolean have_str = FALSE; GVariant *value; const char *key; guint sid; ctx = call_ctx_new (); sid = g_dbus_connection_signal_subscribe (tt->bus, tt->bus_name, "org.freedesktop.DBus.Properties", "PropertiesChanged", tt->obj_path, DBUS_IFACE, G_DBUS_SIGNAL_FLAGS_NONE, props_changed_signal, ctx, NULL); g_assert_cmpuint (sid, >, 0); g_idle_add (change_properties, tt); call_ctx_run (ctx); g_dbus_connection_signal_unsubscribe (tt->bus, sid); g_assert_no_error (ctx->error); g_assert_nonnull (ctx->data); g_variant_get (ctx->data, "a{sv}", &iter); while (g_variant_iter_next (iter, "{&sv}", &key, &value)) { if (bolt_streq (key, "Bool")) { have_bool = TRUE; g_assert_true (g_variant_get_boolean (value) == tt->obj->prop_bool); } else if (bolt_streq (key, "StrRW")) { have_str = TRUE; g_assert_cmpstr (g_variant_get_string (value, NULL), ==, tt->obj->str); } g_variant_unref (value); } g_assert_true (have_bool); g_assert_true (have_str); } static void test_exported_props_enums (TestExported *tt, gconstpointer data) { g_autoptr(CallCtx) ctx = NULL; g_autoptr(GVariant) v = NULL; const char *have; const char *want; ctx = call_ctx_new (); tt->obj->security = BOLT_SECURITY_SECURE; /* valid property value, should be converted to string */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", DBUS_IFACE, "Security"), G_VARIANT_TYPE ("(v)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_nonnull (ctx->data); g_variant_get (ctx->data, "(v)", &v); have = g_variant_get_string (v, NULL); want = bolt_security_to_string (tt->obj->security); g_assert_cmpstr (have, ==, want); /* setter */ bt_exported_install_property_authorizer (tt->obj); tt->obj->authorize_properties = TRUE; have = bolt_security_to_string (BOLT_SECURITY_USER); g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "Security", g_variant_new ("s", have)), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_cmpint (tt->obj->security, ==, BOLT_SECURITY_USER); } static void test_exported_props_flags (TestExported *tt, gconstpointer data) { g_autoptr(CallCtx) ctx = NULL; g_autoptr(GVariant) v = NULL; g_autofree char *want = NULL; g_autofree char *ref = NULL; const char *have = NULL; BoltKittFlags kf = BOLT_KITT_ENABLED | BOLT_KITT_TURBO_BOOST; ctx = call_ctx_new (); tt->obj->kitt = BOLT_KITT_DEFAULT; /* valid property value, should be converted to string */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", DBUS_IFACE, "KittMode"), G_VARIANT_TYPE ("(v)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_nonnull (ctx->data); g_variant_get (ctx->data, "(v)", &v); have = g_variant_get_string (v, NULL); want = bolt_flags_to_string (BOLT_TYPE_KITT_FLAGS, tt->obj->kitt, NULL); g_assert_cmpstr (have, ==, want); /* setter */ bt_exported_install_property_authorizer (tt->obj); tt->obj->authorize_properties = TRUE; ref = bolt_flags_to_string (BOLT_TYPE_KITT_FLAGS, kf, NULL); g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "KittMode", g_variant_new ("s", ref)), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_cmpint (tt->obj->kitt, ==, kf); /* setter with invalid argument */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "KittMode", g_variant_new ("s", "invalid | foobar")), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); } static void test_exported_props_object (TestExported *tt, gconstpointer data) { g_autoptr(CallCtx) ctx = NULL; g_autoptr(GVariant) v = NULL; g_autofree char *want = NULL; const char *have; ctx = call_ctx_new (); /* valid property value, should be converted to string */ g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", DBUS_IFACE, "Object"), G_VARIANT_TYPE ("(v)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_no_error (ctx->error); g_assert_nonnull (ctx->data); g_variant_get (ctx->data, "(v)", &v); have = g_variant_get_string (v, NULL); g_object_get (tt->obj->prop_obj, "id", &want, NULL); g_assert_cmpstr (have, ==, want); /* setter, should be an error (invalid arguments) */ bt_exported_install_property_authorizer (tt->obj); tt->obj->authorize_properties = TRUE; have = bolt_security_to_string (BOLT_SECURITY_USER); g_dbus_connection_call (tt->bus, tt->bus_name, tt->obj_path, "org.freedesktop.DBus.Properties", "Set", g_variant_new ("(ssv)", DBUS_IFACE, "Object", g_variant_new ("s", have)), NULL, G_DBUS_CALL_FLAGS_NONE, 2000, NULL, dbus_call_done, ctx); call_ctx_run (ctx); g_assert_error (ctx->error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); } int main (int argc, char **argv) { int res; setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_resources_register (bolt_test_get_resource ()); g_test_add ("/exported/export", TestExported, NULL, NULL, test_exported_export, NULL); g_test_add ("/exported/basic", TestExported, NULL, test_exported_setup, test_exported_basic, test_exported_teardown); g_test_add ("/exported/props", TestExported, NULL, test_exported_setup, test_exported_props, test_exported_teardown); g_test_add ("/exported/props/changed", TestExported, NULL, test_exported_setup, test_exported_props_changed, test_exported_teardown); g_test_add ("/exported/props/enums", TestExported, NULL, test_exported_setup, test_exported_props_enums, test_exported_teardown); g_test_add ("/exported/props/flags", TestExported, NULL, test_exported_setup, test_exported_props_flags, test_exported_teardown); g_test_add ("/exported/props/object", TestExported, NULL, test_exported_setup, test_exported_props_object, test_exported_teardown); test_bus = g_test_dbus_new (G_TEST_DBUS_NONE); g_test_dbus_up (test_bus); g_assert_nonnull (test_bus); g_debug ("test bus at %s\n", g_getenv ("DBUS_SESSION_BUS_ADDRESS")); res = g_test_run (); g_test_dbus_down (test_bus); g_clear_object (&test_bus); return res; } bolt-0.9.2/tests/test-glue.c000066400000000000000000000610611417453051000157050ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-glue.h" #include "bolt-error.h" #include "bolt-wire.h" #include "test-enums.h" #include "bolt-test-resources.h" #include #include #include #include /* *** Tiny object with only an "id" property (adapted from test-exported.c) */ #define BT_TYPE_ID bt_id_get_type () G_DECLARE_FINAL_TYPE (BtId, bt_id, BT, ID, GObject); struct _BtId { GObject parent; }; G_DEFINE_TYPE (BtId, bt_id, G_TYPE_OBJECT); enum { PROP_ID_0, PROP_ID_OID, PROP_ID_LAST }; static GParamSpec *id_props[PROP_ID_LAST] = {NULL, }; static void bt_id_init (BtId *bi) { } static void bt_id_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_ID_OID: g_value_set_string (value, ""); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bt_id_class_init (BtIdClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bt_id_get_property; id_props[PROP_ID_OID] = g_param_spec_string ("object-id", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_ID_LAST, id_props); } /* *** Glue test object */ #define BT_TYPE_GLUE bt_glue_get_type () G_DECLARE_FINAL_TYPE (BtGlue, bt_glue, BT, GLUE, BtId); struct _BtGlue { BtId parent; }; G_DEFINE_TYPE (BtGlue, bt_glue, BT_TYPE_ID); enum { PROP_GLUE_0, PROP_OBJECT_ID, PROP_ID, PROP_GLUE_LAST }; static GParamSpec *glue_props[PROP_GLUE_LAST] = {NULL, }; static void bt_glue_init (BtGlue *bg) { } static void bt_glue_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_ID: case PROP_OBJECT_ID: g_value_set_string (value, "bt-glue"); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bt_glue_class_init (BtGlueClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = bt_glue_get_property; glue_props[PROP_OBJECT_ID] = bolt_param_spec_override (gobject_class, "object-id"); glue_props[PROP_ID] = g_param_spec_string ("id", "Id", NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_GLUE_LAST, glue_props); } typedef struct { BtGlue *bg; } TestGlue; static void test_glue_setup (TestGlue *tt, gconstpointer data) { tt->bg = g_object_new (BT_TYPE_GLUE, NULL); } static void test_glue_teardown (TestGlue *tt, gconstpointer data) { g_clear_object (&tt->bg); } /* The tests */ static void test_param_spec_override (TestGlue *tt, gconstpointer data) { g_autofree char *oid = NULL; g_autofree char *id = NULL; g_object_get (tt->bg, "id", &id, "object-id", &oid, NULL); g_assert_nonnull (id); g_assert_nonnull (oid); g_assert_cmpstr (id, ==, oid); } static void test_props_basic (TestGlue *tt, gconstpointer data) { g_autoptr(GPtrArray) props = NULL; g_autoptr(GParamSpec) pspec = NULL; g_autoptr(GError) err = NULL; gboolean ok; props = bolt_properties_for_type (BT_TYPE_GLUE); g_assert_nonnull (props); g_assert_cmpuint (props->len, ==, PROP_GLUE_LAST - 2); ok = bolt_properties_find (props, "id", &pspec, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_nonnull (pspec); g_assert_true (pspec == glue_props[PROP_ID]); } static void test_parse_str_by_pspec_bool (TestGlue *tt, gconstpointer data) { g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; gboolean ok; spec = g_param_spec_boolean ("Test", NULL, NULL, TRUE, G_PARAM_STATIC_STRINGS); /* ok: true */ ok = bolt_str_parse_by_pspec (spec, "true", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_BOOLEAN (&val)); g_assert_true (g_value_get_boolean (&val)); /* ok: false */ g_value_reset (&val); ok = bolt_str_parse_by_pspec (spec, "false", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_BOOLEAN (&val)); g_assert_false (g_value_get_boolean (&val)); /* error */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "narf", &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); } static void test_parse_str_by_pspec_uint (TestGlue *tt, gconstpointer data) { g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; gboolean ok; spec = g_param_spec_uint ("UInt", NULL, NULL, 10, 100, 11, G_PARAM_STATIC_STRINGS); /* ok: 10 */ ok = bolt_str_parse_by_pspec (spec, "10", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_UINT (&val)); g_assert_cmpuint (g_value_get_uint (&val), ==, 10); /* ok: false */ g_value_reset (&val); ok = bolt_str_parse_by_pspec (spec, "0x2A", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_UINT (&val)); g_assert_cmpuint (g_value_get_uint (&val), ==, 0x2A); /* error: out of bounds */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "111", &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); /* error: not a number */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "narf", &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); } static void test_parse_str_by_pspec_uint64 (TestGlue *tt, gconstpointer data) { g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; gboolean ok; spec = g_param_spec_uint64 ("UInt64", NULL, NULL, 10, 100, 11, G_PARAM_STATIC_STRINGS); /* ok: 10 */ ok = bolt_str_parse_by_pspec (spec, "10", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_UINT64 (&val)); g_assert_cmpuint (g_value_get_uint64 (&val), ==, 10); /* ok: false */ g_value_reset (&val); ok = bolt_str_parse_by_pspec (spec, "0x2A", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_UINT64 (&val)); g_assert_cmpuint (g_value_get_uint64 (&val), ==, 0x2A); /* error: out of bounds */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "111", &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); /* error: not a number */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "narf", &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); } static void test_parse_str_by_pspec_enum (TestGlue *tt, gconstpointer data) { g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; gboolean ok; spec = g_param_spec_enum ("Enum", NULL, NULL, BOLT_TYPE_TEST_ENUM, BOLT_TEST_ONE, G_PARAM_STATIC_STRINGS); /* ok: valid enum value */ ok = bolt_str_parse_by_pspec (spec, "two", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_ENUM (&val)); g_assert_cmpint (g_value_get_enum (&val), ==, BOLT_TEST_TWO); /* ok: valid enum value */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "unknown", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_ENUM (&val)); g_assert_cmpint (g_value_get_enum (&val), ==, BOLT_TEST_UNKNOWN); /* error: invalid value */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "six", &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); } static void test_parse_str_by_pspec_flags (TestGlue *tt, gconstpointer data) { g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; gboolean ok; spec = g_param_spec_flags ("Flags", NULL, NULL, BOLT_TYPE_KITT_FLAGS, BOLT_KITT_DEFAULT, G_PARAM_STATIC_STRINGS); /* ok: valid enum value */ ok = bolt_str_parse_by_pspec (spec, "enabled", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_FLAGS (&val)); g_assert_cmpuint (g_value_get_flags (&val), ==, BOLT_KITT_ENABLED); /* ok: valid enum value */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "sspm|turbo-boost", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_FLAGS (&val)); g_assert_cmpuint (g_value_get_flags (&val), ==, BOLT_KITT_SSPM | BOLT_KITT_TURBO_BOOST); /* error: invalid value */ g_value_unset (&val); ok = bolt_str_parse_by_pspec (spec, "six", &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_clear_error (&err); } static void test_parse_str_by_pspec_string (TestGlue *tt, gconstpointer data) { g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; gboolean ok; spec = g_param_spec_string ("String", NULL, NULL, "default", G_PARAM_STATIC_STRINGS); /* ok: valid string */ ok = bolt_str_parse_by_pspec (spec, "enabled", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_STRING (&val)); g_assert_cmpstr (g_value_get_string (&val), ==, "enabled"); } static void test_parse_str_by_pspec_strv (TestGlue *tt, gconstpointer data) { g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_auto(GValue) val = G_VALUE_INIT; char **strv = NULL; gboolean ok; spec = g_param_spec_boxed ("StringVector", NULL, NULL, G_TYPE_STRV, G_PARAM_STATIC_STRINGS); /* ok: valid enum value */ ok = bolt_str_parse_by_pspec (spec, "a,b,c", &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (G_VALUE_HOLDS_BOXED (&val)); strv = g_value_get_boxed (&val); g_assert_cmpuint (g_strv_length (strv), ==, 3); g_assert_cmpstr (strv[0], ==, "a"); g_assert_cmpstr (strv[1], ==, "b"); g_assert_cmpstr (strv[2], ==, "c"); } static void test_wire_conv_enum (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) bogus = NULL; g_autoptr(GVariant) var = NULL; g_auto(GValue) val = G_VALUE_INIT; const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; spec = g_param_spec_enum ("test", "Test", "Test Enumeration", BOLT_TYPE_TEST_ENUM, BOLT_TEST_TWO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); conv = bolt_wire_conv_for (G_VARIANT_TYPE_STRING, spec); g_assert_nonnull (conv); wire_type = bolt_wire_conv_get_wire_type (conv); prop_spec = bolt_wire_conv_get_prop_spec (conv); g_assert_false (bolt_wire_conv_is_native (conv)); g_assert_nonnull (bolt_wire_conv_describe (conv)); g_assert_cmpstr ((const char *) wire_type, ==, (const char *) G_VARIANT_TYPE_STRING); g_assert_true (prop_spec == spec); g_value_init (&val, BOLT_TYPE_TEST_ENUM); g_value_set_enum (&val, BOLT_TEST_THREE); /* to the wire */ var = bolt_wire_conv_to_wire (conv, &val, &err); g_assert_no_error (err); g_assert_nonnull (var); g_assert_cmpstr (g_variant_get_string (var, NULL), ==, "three"); /* from the wire, value is unset */ g_value_unset (&val); g_assert_true (G_VALUE_TYPE (&val) == 0); ok = bolt_wire_conv_from_wire (conv, var, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (g_value_get_enum (&val), ==, BOLT_TEST_THREE); /* from the wire, value is preset */ g_value_reset (&val); g_assert_true (G_VALUE_HOLDS (&val, BOLT_TYPE_TEST_ENUM)); ok = bolt_wire_conv_from_wire (conv, var, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (g_value_get_enum (&val), ==, BOLT_TEST_THREE); /* values from the wire can not be trusted */ g_value_reset (&val); g_assert_cmpint (g_value_get_enum (&val), !=, BOLT_TEST_THREE); g_assert_cmpint (g_value_get_enum (&val), !=, BOLT_TEST_TWO); bogus = g_variant_ref_sink (g_variant_new_string ("bogus-bogus")); ok = bolt_wire_conv_from_wire (conv, bogus, &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_assert_cmpint (g_value_get_enum (&val), ==, G_PARAM_SPEC_ENUM (spec)->default_value); } static void test_wire_conv_flags (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) bogus = NULL; g_autoptr(GVariant) var = NULL; g_auto(GValue) val = G_VALUE_INIT; const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; spec = g_param_spec_flags ("test", "Test", "Test Flags", BOLT_TYPE_KITT_FLAGS, BOLT_KITT_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); conv = bolt_wire_conv_for (G_VARIANT_TYPE_STRING, spec); g_assert_nonnull (conv); wire_type = bolt_wire_conv_get_wire_type (conv); prop_spec = bolt_wire_conv_get_prop_spec (conv); g_assert_false (bolt_wire_conv_is_native (conv)); g_assert_nonnull (bolt_wire_conv_describe (conv)); g_assert_cmpstr ((const char *) wire_type, ==, (const char *) G_VARIANT_TYPE_STRING); g_assert_true (prop_spec == spec); g_value_init (&val, BOLT_TYPE_KITT_FLAGS); g_value_set_flags (&val, BOLT_KITT_ENABLED); /* to the wire */ var = bolt_wire_conv_to_wire (conv, &val, &err); g_assert_no_error (err); g_assert_nonnull (var); g_assert_cmpstr (g_variant_get_string (var, NULL), ==, "enabled"); /* from the wire, value is unset */ g_value_unset (&val); g_assert_true (G_VALUE_TYPE (&val) == 0); ok = bolt_wire_conv_from_wire (conv, var, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (g_value_get_flags (&val), ==, BOLT_KITT_ENABLED); /* from the wire, value is preset */ g_value_reset (&val); g_assert_true (G_VALUE_HOLDS (&val, BOLT_TYPE_KITT_FLAGS)); ok = bolt_wire_conv_from_wire (conv, var, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (g_value_get_flags (&val), ==, BOLT_KITT_ENABLED); /* values from the wire can not be trusted */ g_value_reset (&val); bogus = g_variant_ref_sink (g_variant_new_string ("bogus-bogus")); ok = bolt_wire_conv_from_wire (conv, bogus, &val, &err); g_assert_nonnull (err); g_assert_false (ok); g_assert_cmpint (g_value_get_flags (&val), ==, G_PARAM_SPEC_FLAGS (spec)->default_value); } static void test_wire_conv_object (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) bogus = NULL; g_autoptr(GVariant) var = NULL; g_auto(GValue) val = G_VALUE_INIT; const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; spec = g_param_spec_object ("obj", "Obj", "Object Test", BT_TYPE_GLUE, G_PARAM_STATIC_STRINGS); conv = bolt_wire_conv_for (G_VARIANT_TYPE_STRING, spec); g_assert_nonnull (conv); wire_type = bolt_wire_conv_get_wire_type (conv); prop_spec = bolt_wire_conv_get_prop_spec (conv); g_assert_false (bolt_wire_conv_is_native (conv)); g_assert_nonnull (bolt_wire_conv_describe (conv)); g_assert_cmpstr ((const char *) wire_type, ==, (const char *) G_VARIANT_TYPE_STRING); g_assert_true (prop_spec == spec); /* to the wire, empty value (empty prop), which is legal */ g_value_init (&val, BT_TYPE_GLUE); g_value_set_object (&val, NULL); var = bolt_wire_conv_to_wire (conv, &val, &err); g_assert_no_error (err); g_assert_nonnull (var); g_assert_cmpstr (g_variant_get_string (var, NULL), ==, ""); g_clear_pointer (&var, g_variant_unref); /* to the wire, value holding a valid object */ g_value_reset (&val); g_value_set_object (&val, tt->bg); var = bolt_wire_conv_to_wire (conv, &val, &err); g_assert_no_error (err); g_assert_nonnull (var); g_assert_cmpstr (g_variant_get_string (var, NULL), ==, "bt-glue"); /* the other way around does not work */ g_value_reset (&val); bogus = g_variant_ref_sink (g_variant_new_string ("bt-glue")); ok = bolt_wire_conv_from_wire (conv, bogus, &val, &err); g_assert_nonnull (err); g_assert_false (ok); } static void test_wire_conv_simple (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) var = NULL; g_autoptr(GParamSpec) spec = NULL; g_auto(GValue) val = G_VALUE_INIT; const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; spec = g_param_spec_uint64 ("uint", "Uint", "Unsigned Integer", 0, 100, 23, G_PARAM_STATIC_STRINGS); conv = bolt_wire_conv_for (G_VARIANT_TYPE_UINT64, spec); g_assert_nonnull (conv); wire_type = bolt_wire_conv_get_wire_type (conv); prop_spec = bolt_wire_conv_get_prop_spec (conv); g_assert_true (bolt_wire_conv_is_native (conv)); g_assert_nonnull (bolt_wire_conv_describe (conv)); g_assert_cmpstr ((const char *) wire_type, ==, (const char *) G_VARIANT_TYPE_UINT64); g_assert_true (prop_spec == spec); g_value_init (&val, G_TYPE_UINT64); g_value_set_uint64 (&val, 42U); /* to the wire */ var = bolt_wire_conv_to_wire (conv, &val, &err); g_assert_no_error (err); g_assert_nonnull (var); g_assert_cmpuint (g_variant_get_uint64 (var), ==, 42U); /* from the wire, value is unset */ g_value_unset (&val); g_assert_true (G_VALUE_TYPE (&val) == 0); ok = bolt_wire_conv_from_wire (conv, var, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (g_value_get_uint64 (&val), ==, 42U); /* from the wire, value is preset */ g_value_reset (&val); g_assert_true (G_VALUE_HOLDS (&val, G_TYPE_UINT64)); g_assert_cmpuint (g_value_get_uint64 (&val), !=, 42U); ok = bolt_wire_conv_from_wire (conv, var, &val, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpint (g_value_get_uint64 (&val), ==, 42U); } static void test_wire_conv_custom (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) var = NULL; g_auto(GValue) val = G_VALUE_INIT; gboolean ok; BoltLinkSpeed *check; BoltLinkSpeed attr = {.rx.speed = 10, .rx.lanes = 1, .tx.speed = 20, .tx.lanes = 2}; spec = g_param_spec_boxed ("link-speed", "LinkSpeed", "Link Speed Info", BOLT_TYPE_LINK_SPEED, G_PARAM_STATIC_STRINGS); conv = bolt_wire_conv_custom (G_VARIANT_TYPE ("a{su}"), spec, "link speed to dict", bolt_link_speed_to_wire, bolt_link_speed_from_wire); g_assert_nonnull (conv); g_assert_false (bolt_wire_conv_is_native (conv)); g_assert_nonnull (bolt_wire_conv_describe (conv)); g_value_init (&val, BOLT_TYPE_LINK_SPEED); g_value_set_boxed (&val, &attr); /* to the wire */ var = bolt_wire_conv_to_wire (conv, &val, &err); g_assert_no_error (err); g_assert_nonnull (var); /* from the wire, value is unset */ g_value_unset (&val); g_assert_true (G_VALUE_TYPE (&val) == 0); ok = bolt_wire_conv_from_wire (conv, var, &val, &err); g_assert_no_error (err); g_assert_true (ok); check = g_value_get_boxed (&val); g_assert_cmpuint (attr.rx.speed, ==, check->rx.speed); g_assert_cmpuint (attr.rx.lanes, ==, check->rx.lanes); g_assert_cmpuint (attr.tx.speed, ==, check->tx.speed); g_assert_cmpuint (attr.tx.lanes, ==, check->tx.lanes); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_test_add ("/common/param_spec_override", TestGlue, NULL, test_glue_setup, test_param_spec_override, test_glue_teardown); g_test_add ("/common/props_basic", TestGlue, NULL, test_glue_setup, test_props_basic, test_glue_teardown); g_test_add ("/common/str_parse_by_pspec/bool", TestGlue, NULL, NULL, test_parse_str_by_pspec_bool, NULL); g_test_add ("/common/str_parse_by_pspec/uint", TestGlue, NULL, NULL, test_parse_str_by_pspec_uint, NULL); g_test_add ("/common/str_parse_by_pspec/uint64", TestGlue, NULL, NULL, test_parse_str_by_pspec_uint64, NULL); g_test_add ("/common/str_parse_by_pspec/enum", TestGlue, NULL, NULL, test_parse_str_by_pspec_enum, NULL); g_test_add ("/common/str_parse_by_pspec/flags", TestGlue, NULL, NULL, test_parse_str_by_pspec_flags, NULL); g_test_add ("/common/str_parse_by_pspec/string", TestGlue, NULL, NULL, test_parse_str_by_pspec_string, NULL); g_test_add ("/common/str_parse_by_pspec/strv", TestGlue, NULL, NULL, test_parse_str_by_pspec_strv, NULL); g_test_add ("/common/wire_conv/enum", TestGlue, NULL, NULL, test_wire_conv_enum, NULL); g_test_add ("/common/wire_conv/flags", TestGlue, NULL, NULL, test_wire_conv_flags, NULL); g_test_add ("/common/wire_conv/object", TestGlue, NULL, test_glue_setup, test_wire_conv_object, test_glue_teardown); g_test_add ("/common/wire_conv/simple", TestGlue, NULL, NULL, test_wire_conv_simple, NULL); g_test_add ("/common/wire_conv/custom", TestGlue, NULL, NULL, test_wire_conv_custom, NULL); return g_test_run (); } bolt-0.9.2/tests/test-guard.c000066400000000000000000000221131417453051000160460ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-guard.h" #include "bolt-dbus.h" #include "bolt-fs.h" #include "bolt-test.h" #include #include #include #include #include #include #include #include #include #include typedef struct { char *rundir; } TestGuard; static void test_guard_setup (TestGuard *tt, gconstpointer data) { g_autoptr(GError) err = NULL; tt->rundir = g_strdup (g_getenv ("BOLT_RUNDIR")); if (tt->rundir == NULL) tt->rundir = g_dir_make_tmp ("bolt.guard.XXXXXX", &err); g_assert_no_error (err); g_assert_nonnull (tt->rundir); g_debug ("rundir at '%s'", tt->rundir); } static void test_guard_tear_down (TestGuard *tt, gconstpointer user) { g_autoptr(GError) err = NULL; gboolean ok; ok = bolt_fs_cleanup_dir (tt->rundir, &err); g_assert_no_error (err); g_assert_true (ok); g_clear_pointer (&tt->rundir, g_free); } static void on_release_true (BoltGuard *guard, gboolean *released) { g_assert_cmpstr (bolt_guard_get_id (guard), ==, "guard-1"); g_assert_cmpstr (bolt_guard_get_who (guard), ==, "Richard III"); g_assert_cmpuint (bolt_guard_get_pid (guard), ==, getpid ()); *released = TRUE; } static void test_guard_basic (TestGuard *tt, gconstpointer user) { g_autoptr(BoltGuard) guard = NULL; g_autoptr(GError) err = NULL; g_autoptr(GFile) f = NULL; const char *id = "guard-1"; const char *who = "Richard III"; gboolean released = FALSE; gboolean ok; pid_t pid = getpid (); guard = g_object_new (BOLT_TYPE_GUARD, "id", id, "who", who, "pid", pid, NULL); g_assert_nonnull (guard); g_assert_cmpstr (id, ==, bolt_guard_get_id (guard)); g_assert_cmpstr (who, ==, bolt_guard_get_who (guard)); g_assert_cmpuint ((guint) pid, ==, bolt_guard_get_pid (guard)); g_assert_null (bolt_guard_get_path (guard)); g_assert_null (bolt_guard_get_fifo (guard)); f = g_file_new_for_path (tt->rundir); ok = bolt_guard_save (guard, f, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_nonnull (bolt_guard_get_path (guard)); g_signal_connect (guard, "released", (GCallback) on_release_true, &released); g_assert_false (released); /* release the guard */ g_clear_object (&guard); g_assert_true (released); } static gboolean on_cb_close_fd (gpointer user_data) { int *fd = user_data; int r; g_debug ("closing fd"); r = close (*fd); g_assert_cmpint (r, >, -1); *fd = -1; return FALSE; } static void quit_mainloop (BoltGuard *guard, GMainLoop *loop) { g_debug ("Stopping loop"); g_main_loop_quit (loop); } static void test_guard_recover_active (TestGuard *tt, gconstpointer user) { g_autoptr(GPtrArray) guards = NULL; g_autoptr(BoltGuard) guard = NULL; g_autoptr(GMainLoop) loop = NULL; g_autoptr(GError) err = NULL; g_autoptr(GFile) f = NULL; g_autofree char *fifo = NULL; const char *id = "guard-1"; const char *who = "Richard III"; BoltGuard *g = NULL; gboolean released = FALSE; gboolean ok; gpointer data; int fd; guard = g_object_new (BOLT_TYPE_GUARD, "id", id, "who", who, "pid", getpid (), NULL); g_assert_nonnull (guard); f = g_file_new_for_path (tt->rundir); ok = bolt_guard_save (guard, f, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_nonnull (bolt_guard_get_path (guard)); g_signal_connect (guard, "released", (GCallback) on_release_true, &released); g_assert_false (released); fd = bolt_guard_monitor (guard, &err); g_assert_no_error (err); g_assert_cmpint (fd, >, -1); g_object_unref (guard); /* monitor adds a references */ /* memorize the fifo so we can check it exists * after we release the guard */ fifo = g_strdup (bolt_guard_get_fifo (guard)); g_assert_nonnull (fifo); ok = g_file_test (fifo, G_FILE_TEST_EXISTS); g_assert_true (ok); /* release the kraken */ g_clear_object (&guard); g_assert_true (released); ok = g_file_test (fifo, G_FILE_TEST_EXISTS); g_assert_true (ok); /* recover the guard */ guards = bolt_guard_recover (tt->rundir, &err); g_assert_no_error (err); g_assert_cmpuint (guards->len, ==, 1); g = g_ptr_array_index (guards, 0); g_assert_cmpstr (bolt_guard_get_id (g), ==, id); g_assert_cmpstr (bolt_guard_get_who (g), ==, who); g_assert_cmpuint (bolt_guard_get_pid (g), ==, getpid ()); released = FALSE; g_signal_connect (g, "released", (GCallback) on_release_true, &released); loop = g_main_loop_new (NULL, FALSE); g_signal_connect (g, "released", (GCallback) quit_mainloop, loop); /* schedule a closing of the fifo */ g_idle_add (on_cb_close_fd, (gpointer) & fd); /* fail if we don't have anything after n seconds */ bolt_test_run_main_loop (loop, 5, TRUE, NULL); /* free the array without calling its destroy function */ data = g_ptr_array_free (guards, FALSE); g_free (data); guards = NULL; } static void test_guard_recover_dead (TestGuard *tt, gconstpointer user) { g_autoptr(GPtrArray) guards = NULL; g_autoptr(BoltGuard) guard = NULL; g_autoptr(GMainLoop) loop = NULL; g_autoptr(GError) err = NULL; g_autoptr(GFile) f = NULL; g_autofree char *fifo = NULL; const char *id = "guard-1"; const char *who = "Richard III"; BoltGuard *g = NULL; gboolean released = FALSE; gboolean ok; gpointer data; int fd; guard = g_object_new (BOLT_TYPE_GUARD, "id", id, "who", who, "pid", getpid (), NULL); g_assert_nonnull (guard); f = g_file_new_for_path (tt->rundir); ok = bolt_guard_save (guard, f, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_nonnull (bolt_guard_get_path (guard)); g_signal_connect (guard, "released", (GCallback) on_release_true, &released); g_assert_false (released); fd = bolt_guard_monitor (guard, &err); g_assert_no_error (err); assert (fd > -1); /* plain assert for coverity */ g_object_unref (guard); /* monitor adds a references */ /* memorize the fifo so we can check it exists * after we release the guard */ fifo = g_strdup (bolt_guard_get_fifo (guard)); g_assert_nonnull (fifo); ok = g_file_test (fifo, G_FILE_TEST_EXISTS); g_assert_true (ok); /* release the kraken */ g_clear_object (&guard); g_assert_true (released); ok = g_file_test (fifo, G_FILE_TEST_EXISTS); g_assert_true (ok); /* simulate that the client meanwhile closed the FIFO */ (void) close (fd); /* recover the guard */ guards = bolt_guard_recover (tt->rundir, &err); g_assert_no_error (err); g_assert_cmpuint (guards->len, ==, 1); g = g_ptr_array_index (guards, 0); g_assert_cmpstr (bolt_guard_get_id (g), ==, id); g_assert_cmpstr (bolt_guard_get_who (g), ==, who); g_assert_cmpuint (bolt_guard_get_pid (g), ==, getpid ()); released = FALSE; g_signal_connect (g, "released", (GCallback) on_release_true, &released); loop = g_main_loop_new (NULL, FALSE); g_signal_connect (g, "released", (GCallback) quit_mainloop, loop); /* fail if we don't have anything after n seconds */ bolt_test_run_main_loop (loop, 5, TRUE, NULL); /* free the array without calling its destroy function */ data = g_ptr_array_free (guards, FALSE); g_free (data); guards = NULL; } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/guard/basic", TestGuard, NULL, test_guard_setup, test_guard_basic, test_guard_tear_down); g_test_add ("/guard/recover/active", TestGuard, NULL, test_guard_setup, test_guard_recover_active, test_guard_tear_down); g_test_add ("/guard/recover/dead", TestGuard, NULL, test_guard_setup, test_guard_recover_dead, test_guard_tear_down); return g_test_run (); } bolt-0.9.2/tests/test-integration000077500000000000000000003057701417453051000170660ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- # # bolt integration test suite # # Copyright © 2017 Red Hat, Inc # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . # Authors: # Christian J. Kellner # # pylint: disable=too-many-lines, too-many-statements, too-many-public-methods # pylint: disable=too-many-instance-attributes import binascii import errno import gc import os import shutil import socket import sys import subprocess import unittest import uuid import tempfile import time from collections import namedtuple from contextlib import contextmanager from functools import reduce from itertools import chain try: import gi from gi.repository import GLib from gi.repository import Gio gi.require_version('UMockdev', '1.0') from gi.repository import UMockdev import dbus import dbusmock import configparser except ImportError as e: sys.stderr.write('Skipping integration test due to missing dependencies: %s\n' % str(e)) sys.exit(1) DBUS_NAME = 'org.freedesktop.bolt' DBUS_PATH = '/org/freedesktop/bolt' DBUS_IFACE_PREFIX = 'org.freedesktop.bolt1.' DBUS_IFACE_MANAGER = DBUS_IFACE_PREFIX + 'Manager' DBUS_IFACE_DEVICE = DBUS_IFACE_PREFIX + 'Device' DBUS_IFACE_DOMAIN = DBUS_IFACE_PREFIX + 'Domain' SERVICE_FILE = '/usr/share/dbus-1/system-services/org.freedesktop.bolt.service' def get_timeout(topic='default'): vals = { 'valgrind': { 'default': 20, 'daemon_start': 60 }, 'default': { 'default': 3, 'daemon_start': 5 } } valgrind = os.getenv('VALGRIND') lut = vals['valgrind' if valgrind is not None else 'default'] if topic not in lut: raise ValueError('invalid topic') return lut[topic] def can_override_dac(): """Check if we have CAP_DAC_OVERRIDE""" with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, "access-check") with open(path, "w") as f: f.write("") mode = os.stat(path).st_mode os.chmod(path, 0) can_write = os.access(path, os.W_OK) os.chmod(path, mode & 0o7777) return can_write class Signal: def __init__(self, name): self.name = name self.callbacks = set() self._bridge = None def connect(self, callback): self.callbacks.add(callback) if len(self.callbacks) == 1: self._bridge_build() def disconnect(self, callback): self.callbacks.remove(callback) if not self.callbacks: self._bridge_destory() def disconnect_all(self): self.callbacks = set() self._bridge_destory() def emit(self, *args, **kwargs): res = [cb(*args, **kwargs) for cb in self.callbacks] return any(res) def bridge(self, obj, name, callback): if self._bridge is not None: raise ValueError('already bridged') self._bridge = {'object': obj, 'name': name} if callback is not None: self._bridge['filter'] = callback def birdge_destroy(self): self._bridge = None def _bridge_build(self): if self._bridge is None: return b = self._bridge signal_id = b['object'].connect(b['name'], self._bridge_signal) b['signal_id'] = signal_id def _bridge_destory(self): if self._bridge is None: return b = self._bridge b['object'].disconnect(b['signal_id']) del b['signal_id'] def _bridge_signal(self, *args, **kwargs): if 'filter' in self._bridge: res, args, kwargs = self._bridge['filter'](args, kwargs) if not res: return False return self.emit(*args, **kwargs) def __call__(self, *args, **kwargs): return self.emit(*args, **kwargs) def __iadd__(self, callback): self.connect(callback) return self def __isub__(self, callback): self.disconnect(callback) return self @staticmethod def enable(klass): lst = getattr(klass, 'signals', []) methods = [m for m in dir(klass) if not m.startswith('__')] def install(l): if l is None: return l if l in methods: print('WARNING: signal "%s" will overwrite method' % l, file=sys.stderr) def get_signals(self): signals = getattr(self, '__signals', None) if signals is None: signals = {} setattr(self, '__signals', signals) return signals def get_signal(self): signals = get_signals(self) if l not in signals: signals[l] = Signal(l) return signals[l] def getter(self): return get_signal(self) def setter(self, _value): return get_signal(self) p = property(getter, setter) setattr(klass, l, p) return l bases = klass.__bases__ ps = {s for b in bases for s in getattr(b, 'signals', [])} klass.signals = list(ps.union({install(l) for l in lst})) return klass class Recorder: Event = namedtuple('Event', ['what', 'name', 'details', 'time']) def __init__(self, target): self.recording = True self.event = Signal('event') self.events = [] self.target = target self.target.g_properties_changed += self._on_props_changed self.target.g_signal += self._on_signal def close(self): if not self.recording: return None self.target.g_properties_changed -= self._on_props_changed self.target.g_signal -= self._on_signal self.recording = False return self.events def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() def _on_props_changed(self, props): now = time.time() for k, v in props.items(): event = self.Event('property', k, v, now) self._add_event(event) def _on_signal(self, _proxy, _sender, signal, params): now = time.time() event = self.Event('signal', signal, params, now) self._add_event(event) def _add_event(self, event): self.events.append(event) self.event.emit(event) @staticmethod def event_match(event, target): if event.what != target.what or event.name != target.name: return False if target.details is None: return True return event.details == target.details @staticmethod def events_list_has_event(events, target): return list(filter(lambda x: Recorder.event_match(x, target), events)) @staticmethod def events_list_contains(events, what, name, details=None): target = Recorder.Event(what, name, details, None) return list(filter(lambda x: Recorder.event_match(x, target), events)) def events_filter(self, event): return self.events_list_has_event(self.events, event) def have_event(self, event): return len(self.events_filter(event)) > 0 def wait_for_events(self, lst, timeout=None): loop = GLib.MainLoop() def got_event(event): for idx, current in enumerate(lst): if self.event_match(event, current): del lst[idx] break if not lst: loop.quit() def got_timeout(): print('WARNING: timeout reached! Want: %s, Have: %s' % (str(lst), str(self.events)), file=sys.stderr) loop.quit() # check if we did receive the events already lst = list(filter(lambda x: not self.have_event(x), lst)) if not lst: return True self.event += got_event timeout = timeout or get_timeout() GLib.timeout_add(timeout*1000, got_timeout) loop.run() self.event -= got_event return len(lst) == 0 def wait_for_event(self, what, name, details=None): event = self.Event(what, name, details, None) return self.wait_for_events([event]) def wait_for_props(self, **kwargs): events = [self.Event('property', name, details, None) for name, details in kwargs.items()] res = self.wait_for_events(events) assert res @Signal.enable class ProxyWrapper: signals = ['g_properties_changed', 'g_signal'] def __init__(self, bus, iname, path): self._proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, DBUS_NAME, path, iname, None) def props_changed(args, _kwargs): return True, [args[1].unpack()], {} self.g_properties_changed.bridge(self._proxy, 'g-properties-changed', props_changed) self.g_signal.bridge(self._proxy, 'g-signal', None) def __getattr__(self, name): if name.startswith('_'): raise AttributeError if '_' in name: c = name.split('_') name = "".join(x.title() for x in c) else: name = name[0].upper() + name[1:] if name in self._proxy.get_cached_property_names(): value = self._proxy.get_cached_property(name) if value is not None: return value.unpack() return value return getattr(self._proxy, name) def record(self): return Recorder(self) @property def object_path(self): return self._proxy.get_object_path() class BoltDevice(ProxyWrapper): UNKNOWN = -1 DISCONNECTED = 0 CONNECTED = 1 CONNECTING = 2 AUTHORIZING = 3 AUTH_ERROR = 4 AUTHORIZED = 5 KEY_MISSING = 0 KEY_HAVE = 1 KEY_NEW = 2 NOPCIE = 1 << 0 SECURE = 1 << 1 NOKEY = 1 << 2 BOOT = 1 << 3 HOST = 'host' PERIPHERAL = 'peripheral' def __init__(self, bus, path): super(BoltDevice, self).__init__(bus, DBUS_IFACE_DEVICE, path) def _set_property(self, name, value): if isinstance(value, str): value = GLib.Variant("s", value) target = GLib.Variant("(ssv)", (DBUS_IFACE_DEVICE, name, value)) res = self._proxy.call_sync('org.freedesktop.DBus.Properties.Set', target, 0, -1, None) return res is not None @property def is_connected(self): return self.status > self.DISCONNECTED @property def is_authorized(self): return self.status >= self.AUTHORIZED def authorize(self, flags=""): self.Authorize('(s)', flags) return True @property def status(self): res = getattr(self, 'Status') mapping = {'unknown': self.UNKNOWN, 'disconnected': self.DISCONNECTED, 'connecting': self.CONNECTING, 'connected': self.CONNECTED, 'authorizing': self.AUTHORIZING, 'auth-error': self.AUTH_ERROR, 'authorized': self.AUTHORIZED} return mapping.get(res, self.UNKNOWN) @property def authflags(self): res = getattr(self, 'AuthFlags') mapping = {'none': 0, 'nopcie': self.NOPCIE, 'secure': self.SECURE, 'nokey': self.NOKEY, 'boot': self.BOOT} print(res, file=sys.stderr) keys = [x.strip() for x in res.split('|')] return reduce(lambda r, x: r | mapping.get(x, 0), keys, 0) @property def device_type(self): return getattr(self, 'type') @property def key(self): res = getattr(self, 'Key') mapping = {'missing': self.KEY_MISSING, 'have': self.KEY_HAVE, 'new': self.KEY_NEW} return mapping.get(res, self.MISSING) @property def label(self): res = getattr(self, 'Label') if not res: return None return res @label.setter def label(self, value): return self._set_property('Label', value) @property def policy(self): res = getattr(self, 'Policy') return res if res else None @policy.setter def policy(self, value): return self._set_property('Policy', value) @property def linkspeed(self): res = getattr(self, 'LinkSpeed') if not res: return None return res @staticmethod def gen_object_path(object_id): base = os.path.join(DBUS_PATH, "devices") return BoltClient.gen_object_path(base, object_id) class BoltDomain(ProxyWrapper): SECURITY_NONE = 'none' SECURITY_USER = 'user' SECURITY_SECURE = 'secure' SECURITY_DPONLY = 'dponly' SECURITY_USBONLY = 'usbonly' @classmethod def security_levels(cls): v = vars(cls) return [v[k] for k in v.keys() if k.startswith('SECURITY_')] def __init__(self, bus, path): super(BoltDomain, self).__init__(bus, DBUS_IFACE_DOMAIN, path) @property def bootacl(self): res = getattr(self, 'BootACL') if not res: return None return res @bootacl.setter def bootacl(self, value): value = GLib.Variant("as", value) self._proxy.call_sync('org.freedesktop.DBus.Properties.Set', GLib.Variant("(ssv)", (DBUS_IFACE_DOMAIN, "BootACL", value)), 0, -1, None) @Signal.enable class BoltClient(ProxyWrapper): signals = ['device_added', 'device_removed'] POLICY_DEFAULT = 'default' POLICY_MANUAL = 'manual' POLICY_AUTO = 'auto' POLICY_IOMMU = 'iommu' def __init__(self, bus): super(BoltClient, self).__init__(bus, DBUS_IFACE_MANAGER, DBUS_PATH) self._proxy.connect('g-signal', self._on_dbus_signal) def _on_dbus_signal(self, _proxy, _sender, signal, params): bus = self._proxy.get_connection() if signal == 'DeviceAdded': self.device_added.emit(BoltDevice(bus, params[0])) return True if signal == 'DeviceRemoved': self.device_removed.emit(params[0]) return True return False @property def auth_mode(self): res = getattr(self, 'AuthMode') if not res: return None return res @auth_mode.setter def auth_mode(self, value): if isinstance(value, str): value = GLib.Variant("s", value) self._proxy.call_sync('org.freedesktop.DBus.Properties.Set', GLib.Variant("(ssv)", (DBUS_IFACE_MANAGER, "AuthMode", value)), 0, -1, None) def list_domains(self): domains = self.ListDomains() if domains is None: return None bus = self._proxy.get_connection() return [BoltDomain(bus, d) for d in domains] def domain_by_id(self, uid): object_path = self.DomainById("(s)", uid) if object_path is None: return None bus = self._proxy.get_connection() return BoltDomain(bus, object_path) def list_devices(self): devices = self.ListDevices() if devices is None: return None bus = self._proxy.get_connection() return [BoltDevice(bus, d) for d in devices] def device_by_uid(self, uid): object_path = self.DeviceByUid("(s)", uid) if object_path is None: return None bus = self._proxy.get_connection() return BoltDevice(bus, object_path) def list_peripherals(self): devs = self.list_devices() return list(filter(lambda d: d.device_type == BoltDevice.PERIPHERAL, devs)) def enroll(self, uid, policy=POLICY_DEFAULT, flags=""): object_path = self.EnrollDevice("(sss)", uid, policy, flags) if object_path is None: return None bus = self._proxy.get_connection() return BoltDevice(bus, object_path) def forget(self, uid): self.ForgetDevice("(s)", uid) return True @staticmethod def gen_object_path(base, object_id): oid = None if object_id: oid = object_id.replace('-', '_') if base and oid: return os.path.join('/', base, oid) if base: return os.path.join('/', base) if oid: return os.path.join('/', oid) return '/' # Mock Device Tree @Signal.enable class Device: subsystem = "unknown" udev_attrs = [] udev_props = [] signals = ['device_connected', 'device_disconnected'] def __init__(self, name, children): self._parent = None self.children = [self._adopt(c) for c in children] self.udev = None self.name = name self.syspath = None def _adopt(self, device): device.parent = self return device def _get_own(self, items): def get_pair(a): v = getattr(self, a.lower()) return [a, str(v) if v is not None else v] x = [get_pair(a) for a in items] i = chain.from_iterable(filter(lambda x: x[1] is not None, x)) return list(i) def collect(self, predicate): children = self.children head = [self] if predicate(self) else [] tail = chain.from_iterable(c.collect(predicate) for c in children) return head + list(filter(predicate, tail)) def first(self, predicate): if predicate(self): return self for c in self.children: found = c.first(predicate) if found: return found return None @property def parent(self): return self._parent @parent.setter def parent(self, value): self._parent = value @property def root(self): return self if self.parent is None else self.parent.root def connect_tree(self, bed): self.connect(bed) for c in self.children: c.connect_tree(bed) def connect(self, bed): print('connecting ' + self.name, file=sys.stderr) assert self.syspath is None attributes = self._get_own(self.udev_attrs) properties = self._get_own(self.udev_props) sysparent = self.parent and self.parent.syspath self.syspath = bed.add_device(self.subsystem, self.name, sysparent, attributes, properties) self.root.device_connected(self) self.testbed = bed def disconnect(self, bed): print('disconnecting ' + self.name, file=sys.stderr) for c in self.children: c.disconnect(bed) bed.uevent(self.syspath, "remove") bed.remove_device(self.syspath) self.root.device_disconnected(self) self.syspath = None self.testbed = None class TbNativeHostInterface(Device): subsystem = "pci" udev_attrs = ['class', 'device', 'vendor'] udev_props = ['DRIVER'] driver = "thunderbolt" def __init__(self, domain, pci_id=0x15d2): assert domain name = "pci0000:00:0d.%d" % domain.index super().__init__(name, [domain]) setattr(self, 'class', '0x088000') self.vendor = '0x8086' self.device = '0x%04x' % pci_id class TbDevice(Device): subsystem = "thunderbolt" devtype = "thunderbolt_device" udev_attrs = ['authorized', 'device', 'device_name', 'generation', 'key', 'unique_id', 'vendor', 'vendor_name', 'rx_lanes', 'rx_speed', 'tx_lanes', 'tx_speed'] udev_props = ['DEVTYPE'] def __init__(self, name, authorized=0, vendor=None, uid=None, children=None, key='\n', gen=None): super(TbDevice, self).__init__(name, children or []) self.unique_id = uid or str(uuid.uuid4()) self.device_name = 'Thunderbolt ' + name self.device = self._make_id(self.device_name) self.vendor_name = vendor or 'GNOME.org' self.vendor = self._make_id(self.vendor_name) self.authorized = authorized self.key = key self.generation = gen self.rx_lanes = None self.rx_speed = None self.tx_lanes = None self.tx_speed = None def disconnect(self, bed): super().disconnect(bed) self.authorized = 0 if self.key is not None: self.key = "\n" @staticmethod def _make_id(name): return '0x%X' % binascii.crc32(name.encode('utf-8')) @property def authorized_file(self): if self.syspath is None: return None return os.path.join(self.syspath, 'authorized') @property def key_file(self): if self.syspath is None: return None return os.path.join(self.syspath, 'key') @property def bolt_status(self): if self.syspath is None: return BoltDevice.DISCONNECTED if self.authorized == 0: return BoltDevice.CONNECTED if self.authorized in [1, 2]: return BoltDevice.AUTHORIZED return BoltDevice.UNKNOWN @property def bolt_authflags(self): flags = 0 if self.syspath is None: return 0 if self.authorized == 2: flags |= BoltDevice.SECURE if self.domain.security == TbDomain.SECURITY_SECURE: if self.key is None: flags |= BoltDevice.NOKEY elif self.domain.security in [TbDomain.SECURITY_DPONLY, TbDomain.SECURITY_USBONLY]: flags |= BoltDevice.NOPCIE return flags @property def domain(self): return self.parent.domain @staticmethod def is_unauthorized(d): return isinstance(d, TbDevice) and d.authorized == 0 @property def bus_path(self): return BoltDevice.gen_object_path(self.unique_id) @property def linkspeed(self): return self.rx_lanes, self.rx_speed, self.tx_lanes, self.tx_speed @linkspeed.setter def linkspeed(self, ls): self.rx_lanes = str(ls["rx.lanes"]) self.rx_speed = "%u.0 Gb/s" % ls["rx.speed"] self.tx_lanes = str(ls["tx.lanes"]) self.tx_speed = "%u.0 Gb/s" % ls["tx.speed"] if self.syspath: self.testbed.set_attribute(self.syspath, "rx_lanes", self.rx_lanes) self.testbed.set_attribute(self.syspath, "rx_speed", self.rx_speed) self.testbed.set_attribute(self.syspath, "tx_lanes", self.tx_lanes) self.testbed.set_attribute(self.syspath, "tx_speed", self.tx_speed) self.testbed.uevent(self.syspath, 'change') def reload_auth(self): authorized = self.authorized key = self.key f = self.authorized_file with open(self.authorized_file, 'r') as f: data = f.read() self.authorized = int(data) with open(os.path.join(self.syspath, 'key'), 'r') as f: self.key = f.read().strip() if self.authorized != authorized or self.key != key: if self.syspath: self.testbed.uevent(self.syspath, 'change') def authorize(self, level): with open(self.authorized_file, 'w') as f: f.write(level) self.reload_auth() def writekey(self, key): with open(self.key_file, 'w') as f: f.write(key) class TbHost(TbDevice): def __init__(self, children, name='Laptop', gen=None): super(TbHost, self).__init__(name, authorized=1, gen=gen, children=children) def connect(self, bed): self.authorized = 1 super(TbHost, self).connect(bed) class TbDomain(Device): subsystem = "thunderbolt" devtype = "thunderbolt_domain" udev_attrs = ['security', 'boot_acl', 'iommu_dma_protection'] udev_props = ['DEVTYPE'] SECURITY_NONE = 'none' SECURITY_USER = 'user' SECURITY_SECURE = 'secure' SECURITY_DPONLY = 'dponly' SECURITY_USBONLY = 'usbonly' def __init__(self, security=SECURITY_SECURE, index=0, host=None, acl=None, iommu=None): assert host assert isinstance(host, TbHost) name = 'domain%d' % index if host.unique_id is None: host.unique_id = '3b7d4bad-4fdf-44ff-8730-ffffdeadbab%d' % index super(TbDomain, self).__init__(name, children=[host]) self.security = security self.boot_acl = ','*(acl-1) if isinstance(acl, int) else acl self.iommu_dma_protection = iommu self.index = index self.nhi = TbNativeHostInterface(self) def connect(self, bed): self.nhi.connect(bed) super().connect(bed) def disconnect(self, bed): if self.syspath: # prevent recursion via nhi.disconnect() super().disconnect(bed) self.nhi.disconnect(bed) @property def unique_id(self): return self.host and self.host.unique_id @property def host(self): if not self.children: return None return self.children[0] @property def devices(self): return self.collect(lambda c: isinstance(c, TbDevice)) @property def peripherals(self): return self.collect(lambda c: isinstance(c, TbDevice) and not isinstance(c, TbHost)) @property def domain(self): return self @property def iommu(self): return self.iommu_dma_protection @staticmethod def checkattr(d, k, v): return hasattr(d, k) and getattr(d, k) == v def find(self, include_domain, **kwargs): def finder(d): if not include_domain and isinstance(d, TbDomain): return False return all([self.checkattr(d, k, v) for k, v in kwargs.items()]) return self.first(finder) class Parameterize: # class is used for namespacing @staticmethod def _make_test(klass, name, func): params = getattr(func, '__expand') def test_function(self): func(self, **params) suffix = '_' + '_'.join(params.keys()) setattr(klass, name + suffix, test_function) @staticmethod def enable(klass): tests = [(m, getattr(klass, m)) for m in dir(klass) if m.startswith('test_')] expandable = filter(lambda f: hasattr(f[1], '__expand'), tests) for t in expandable: Parameterize._make_test(klass, t[0], t[1]) map(lambda t: Parameterize._make_test(klass, t[0], t[1]), expandable) return klass @staticmethod def make(**kwargs): def decorator(func): setattr(func, '__expand', kwargs) return func return decorator # Systemd integration tests class SdNotify: def __init__(self, tmpdir): assert(tmpdir is not None) soflags = socket.SOCK_DGRAM | socket.SOCK_CLOEXEC | socket.SOCK_NONBLOCK self.sock = socket.socket(socket.AF_UNIX, soflags) self.path = os.path.join(tmpdir, 'bolt_notify_socket') self.sock.bind(self.path) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1) self.msgs = [] def read_msg(self): rflags = socket.MSG_DONTWAIT | socket.MSG_CMSG_CLOEXEC | socket.MSG_TRUNC try: msg, _ac, _flags, _addr = self.sock.recvmsg(4096, 0, rflags) except OSError as e: if e.errno != errno.EINTR and e.errno != errno.EAGAIN: raise e msg = None if msg is None: return None msg = msg.decode('utf-8') self.msgs.append(msg) return msg def fetch_msgs(self): count = 0 while self.read_msg(): count += 1 return count def wait_for(self, event, timeout=None): timeout = timeout or get_timeout() timeout_count = timeout * 10 timeout_sleep = 0.1 while timeout_count > 0: count = self.fetch_msgs() if count: matches = [msg for msg in self.msgs if msg.startswith(event)] if matches: return matches time.sleep(timeout_sleep) timeout_count -= 1 # debug print for msg in self.msgs: print(msg) raise TimeoutError("Timeout waiting for '%s'" % event) def close(self): self.path = None if self.sock is None: return self.sock.shutdown(socket.SHUT_WR) self.sock.close() self.sock = None # Test Suite @Parameterize.enable class BoltTest(dbusmock.DBusTestCase): @staticmethod def path_from_service_file(_sf): with open(SERVICE_FILE) as f: for line in f: if not line.startswith('Exec='): continue return line.split('=', 1)[1].strip() return None @classmethod def setUpClass(cls): boltd = None boltctl = None if 'BOLT_BUILD_DIR' in os.environ: print('Testing local build') build_dir = os.environ['BOLT_BUILD_DIR'] boltd = os.path.join(build_dir, 'boltd') boltctl = os.path.join(build_dir, 'boltctl') elif 'UNDER_JHBUILD' in os.environ: print('Testing JHBuild version') jhbuild_prefix = os.environ['JHBUILD_PREFIX'] boltd = os.path.join(jhbuild_prefix, 'libexec', 'boltd') boltctl = os.path.join(jhbuild_prefix, 'bin', 'boltctl') else: print('Testing installed system binaries') boltd = BoltTest.path_from_service_file(SERVICE_FILE) boltctl = shutil.which('boltctl') assert boltd is not None, 'failed to find daemon' assert os.access(boltctl, os.X_OK), "could not execute @ " + boltctl cls.paths = {'daemon': boltd, 'boltctl': boltctl} cls.test_bus = Gio.TestDBus.new(Gio.TestDBusFlags.NONE) cls.test_bus.up() try: del os.environ['DBUS_SESSION_BUS_ADDRESS'] except KeyError: pass os.environ['DBUS_SYSTEM_BUS_ADDRESS'] = cls.test_bus.get_bus_address() cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) # a well known key that can be used in testing cls.key = 'b68bce095a13ac39e9254a88b189a38f240487aa6f78f803390a0cdeceb774d8' # monkey patch Gio.IOErrorEnum to have a.quark() method # so it behaves like Gio.DBusError io_error_quark = GLib.quark_from_static_string('g-io-error-quark') setattr(Gio.IOErrorEnum, 'quark', lambda _self: io_error_quark) @classmethod def tearDownClass(cls): cls.test_bus.down() dbusmock.DBusTestCase.tearDownClass() def setUp(self): self.testbed = UMockdev.Testbed.new() self.assertTrue(UMockdev.in_mock_environment()) self.dbpath = tempfile.mkdtemp() self.rundir = tempfile.mkdtemp() os.makedirs(os.path.join(self.dbpath, 'devices')) os.makedirs(os.path.join(self.dbpath, 'domains')) os.makedirs(os.path.join(self.dbpath, 'keys')) self.client = None self.log = None self.daemon = None self.polkitd = None self.valgrind = False self.sdnotify = None def tearDown(self): shutil.rmtree(self.dbpath) shutil.rmtree(self.rundir) del self.testbed self.daemon_stop() self.polkitd_stop() if self.sdnotify is not None: self.sdnotify.close() self.sdnotify = None # Gross work-around for some flaky behavior where tests will fail # with some obscure error in umockdev, but only if all the tests # executed in one go. Maybe some cleanup issue. Needs further # investigation. gc.collect() # dbus helper methods def get_dbus_property(self, name, interface=DBUS_IFACE_MANAGER): proxy = Gio.DBusProxy.new_sync(self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, DBUS_NAME, DBUS_PATH, 'org.freedesktop.DBus.Properties', None) return proxy.Get('(ss)', interface, name) # daemon helper def daemon_start(self, sdnotify=False): timeout = get_timeout('daemon_start') # seconds env = os.environ.copy() env['G_DEBUG'] = 'fatal-criticals' env['UMOCKDEV_DIR'] = self.testbed.get_root_dir() env['STATE_DIRECTORY'] = self.dbpath env['RUNTIME_DIRECTORY'] = self.rundir if sdnotify: self.sdnotify = SdNotify(self.rundir) env['NOTIFY_SOCKET'] = self.sdnotify.path argv = [self.paths['daemon'], '-v'] valgrind = os.getenv('VALGRIND') if valgrind is not None: argv.insert(0, 'valgrind') argv.insert(1, '--leak-check=full') if os.path.exists(valgrind): argv.insert(2, '--suppressions=%s' % valgrind) self.valgrind = True self.daemon = subprocess.Popen(argv, env=env, stdout=self.log, stderr=subprocess.STDOUT) timeout_count = timeout * 10 timeout_sleep = 0.1 while timeout_count > 0: time.sleep(timeout_sleep) timeout_count -= 1 try: self.get_dbus_property('Version') break except GLib.GError: pass else: timeout_time = timeout * 10 * timeout_sleep self.fail('daemon did not start in %d seconds' % timeout_time) self.client = BoltClient(self.dbus) self.assertEqual(self.daemon.poll(), None, 'daemon crashed') def daemon_stop(self): if self.daemon: try: self.daemon.terminate() except OSError: pass self.daemon.wait() self.daemon = None self.client = None def polkitd_start(self): self._polkitd, self._polkitd_obj = self.spawn_server_template( 'polkitd', {}, stdout=subprocess.DEVNULL) self.polkitd = dbus.Interface(self._polkitd_obj, dbusmock.MOCK_IFACE) def polkitd_stop(self): if self.polkitd is None: return self._polkitd.terminate() self._polkitd.wait() self.polkitd = None def user_config(self, **kwargs): cfg = configparser.ConfigParser() cfg.optionxform = lambda option: option cfg['config'] = {} for k, v in kwargs.items(): cfg['config'][k] = v path = os.path.join(self.dbpath, 'boltd.conf') with open(path, 'w') as f: cfg.write(f) with open(path, 'r') as f: print(f.read()) # executing boltctl def boltctl(self, *args): env = os.environ.copy() env['G_MESSAGES_DEBUG'] = "all" args = [self.paths['boltctl']] + list(args) print('Calling: ' + " ".join(args), file=sys.stderr) process = subprocess.Popen(args, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() ret = process.returncode if ret != 0: print('OUTPUT:') print(stdout, file=sys.stdout) print(stderr, file=sys.stderr) return stdout, stderr, ret # mock tree stuff @staticmethod def default_mock_tree(acl=None): # default mock tree mt = TbDomain(host=TbHost([ TbDevice('Cable1'), TbDevice('Cable2'), TbDevice('SSD1') ]), acl=acl) return mt @staticmethod def simple_mock_tree(): mt = TbDomain(host=TbHost([ TbDevice('Dock') ])) return mt def assertGError(self, have, want): if hasattr(have, 'exception'): have = have.exception domain = GLib.quark_to_string(want.quark()) code = int(want) if have.domain == domain and have.code == code: return msg = "want: [%s (%d)], have [%s]" % (want, code, have) raise self.failureException(msg) def assertDeviceEqual(self, local, remote): self.assertTrue(local and remote) self.assertEqual(local.unique_id, remote.uid) self.assertEqual(local.device_name, remote.Name) self.assertEqual(local.vendor_name, remote.Vendor) # if we are "connected" if local.syspath is not None: self.assertEqual(local.syspath, remote.sysfs_path) self.assertTrue(remote.is_connected) # remote.parent is also only valid if we are connected if local.parent is not None and isinstance(local.parent, TbDevice): self.assertEqual(local.parent.unique_id, remote.parent) self.assertEqual(local.bolt_status, remote.status) self.assertEqual(local.bolt_authflags, remote.authflags) if local.generation is not None: self.assertEqual(local.generation, remote.generation) else: self.assertEqual(0, remote.generation) return True def add_domain_host(self, domain=0, security='secure', uid=None, bootacl=15, iommu=None, nhi=0x15d2): if uid is None: uid = str(uuid.uuid4()) nhi = self.testbed.add_device('pci', '0000:00:0d.%d' % domain, None, ['class', '0x088000', 'vendor', '0x8086', 'device', '0x%04x' % nhi], ['DRIVER', 'thunderbolt']) props = ['security', security] if isinstance(bootacl, int): bootacl = ',' * (bootacl-1) if bootacl is not None: props += ['boot_acl', bootacl] if iommu is not None: props += ['iommu_dma_protection', str(iommu) + '\n'] dc = self.testbed.add_device('thunderbolt', 'domain%d' % domain, nhi, props, ['DEVTYPE', 'thunderbolt_domain']) host = self.testbed.add_device('thunderbolt', "%d-0" % domain, dc, ['device_name', 'Host', 'device', '0x23', 'vendor_name', 'GNOME.org', 'vendor', '0x23', 'authorized', '1', 'unique_id', uid], ['DEVTYPE', 'thunderbolt_device']) return dc, host def remove_domain_host(self, domain, host): self.testbed.uevent(host, "remove") self.testbed.remove_device(host) self.testbed.uevent(domain, "remove") self.testbed.remove_device(domain) nhi = os.path.dirname(domain) self.testbed.uevent(nhi, "remove") self.testbed.remove_device(nhi) def add_device(self, parent, devid, name, vendor, domain=0, authorized=1, key='', boot=None): uid = str(uuid.uuid4()) props = ['device_name', name, 'device', '0x23', 'vendor_name', vendor, 'vendor', '0x23', 'authorized', '%d' % authorized, 'unique_id', uid] if key is not None: # The kernel always returns the key with trailing `\n` if not key.endswith('\n'): key += '\n' props += ['key', key] if boot is not None: props += ['boot', boot] d = self.testbed.add_device('thunderbolt', "%d-%d" % (domain, devid), parent, props, ['DEVTYPE', 'thunderbolt_device']) return d, uid def find_device_by_uid(self, lst, uid): x = [x for x in lst if x.uid == uid] self.assertEqual(len(x), 1) return x[0] def store_put_device(self, dev, policy='auto', key=None): df = configparser.ConfigParser() df.optionxform = lambda option: option uid = dev.unique_id df['device'] = { 'name': dev.device_name, 'vendor': dev.vendor_name, 'type': 'host' if isinstance(dev, TbHost) else 'peripheral' } df['user'] = { 'storetime': int(time.time()), 'policy': policy } if dev.generation: df['device']['generation'] = dev.generation path = os.path.join(self.dbpath, 'devices', uid) with open(path, 'w') as f: df.write(f) if key == 'known': key = 'a26d5ad55b011df39ae06cae1fd329babfecac3465fe0a8828d6178f88e59083' elif key == 'device': key = dev.key if key is None: return path = os.path.join(self.dbpath, 'keys', uid) with open(path, 'w') as f: f.write(key) def store_get_device(self, uid): path = os.path.join(self.dbpath, 'devices', uid) df = configparser.ConfigParser() df.read(path) device = df['device'] name = device['name'] vendor = device['vendor'] generation = None if 'generation' in device: generation = int(device['generation']) dtype = device['type'] if dtype == 'host': dev = TbHost([], gen=generation) elif dtype == 'peripheral': dev = TbDevice(name, vendor=vendor, uid=uid, gen=generation) else: raise ValueError('Unknown device type') return dev @contextmanager def store_deny_device(self, uid): path = os.path.join(self.dbpath, 'devices', uid) fd = os.open(path, os.O_RDWR) mode = os.fstat(fd).st_mode try: print('store: denying access to %s' % uid, file=sys.stderr) os.fchmod(fd, 0) yield fd finally: print('store: restoring access to %s' % uid, file=sys.stderr) os.fchmod(fd, mode & 0o7777) os.close(fd) def store_has_domain(self, uid): path = os.path.join(self.dbpath, 'domains', uid) return os.path.exists(path) def store_create_domain(self, *, uid=None, acl=None): if not uid: uid = str(uuid.uuid4()) if acl: bootacl = ",".join(acl) else: bootacl = "" entry = ( "[domain]\n" f"bootacl={bootacl}\n" ) path = os.path.join(self.dbpath, 'domains', uid) with open(path, 'w') as f: f.write(entry) return uid def store_create_journal(self, name, uid, entries): ts = 123456 data = ["%s %s %016iX" % (e["uid"], e["op"], ts) for e in entries] journal = os.path.join(self.dbpath, name) os.makedirs(journal, exist_ok=True) path = os.path.join(journal, uid) with open(path, 'w') as f: f.write("\n".join(data)) # the actual tests def test_basic(self): def make_events(name, devices): paths = [GLib.Variant("(o)", (dev.bus_path, )) for dev in devices] return [Recorder.Event('signal', name, path, None) for path in paths] self.daemon_start() version = self.client.version assert version is not None d = self.client.list_devices() self.assertEqual(len(d), 0) policy = self.client.default_policy self.assertIn(policy, [self.client.POLICY_AUTO, self.client.POLICY_MANUAL]) # connect all device and make sure we get the proper events tree = self.default_mock_tree() with self.client.record() as tape: events = make_events('DeviceAdded', tree.devices) tree.connect_tree(self.testbed) res = tape.wait_for_events(events) self.assertTrue(res) devices = self.client.list_devices() self.assertEqual(len(devices), len(tree.devices)) for remote in devices: local = tree.find(False, unique_id=remote.uid) self.assertDeviceEqual(local, remote) # disconnect all devices again (don't check the host, i.e. peripherals only) with self.client.record() as tape: events = make_events('DeviceRemoved', tree.peripherals) tree.disconnect(self.testbed) res = tape.wait_for_events(events) self.assertTrue(res) devices = self.client.list_peripherals() self.assertEqual(len(devices), 0) # host device should be stored remote = self.client.device_by_uid(tree.host.unique_id) self.assertEqual(remote.stored, True) self.assertEqual(remote.policy, BoltClient.POLICY_MANUAL) self.daemon_stop() def test_auth_mode(self): _, host = self.add_domain_host(security='secure') self.daemon_start() self.polkitd_start() self.polkitd.SetAllowed(['org.freedesktop.bolt.manage', 'org.freedesktop.bolt.enroll', 'org.freedesktop.bolt.authorize']) client = self.client self.assertEqual(client.auth_mode, 'enabled') # disable the authorization with client.record() as tape: client.auth_mode = 'disabled' tape.wait_for_props(AuthMode='disabled') self.assertEqual(client.auth_mode, 'disabled') _, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=0, key='', boot='0') _, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=0, key=None, boot='0') devices = self.client.list_devices() self.assertEqual(len(devices), 3) d1_remote = self.find_device_by_uid(devices, d1_uid) d2_remote = self.find_device_by_uid(devices, d2_uid) before = int(time.time()) remotes = [(d1_remote, d1_uid), (d2_remote, d2_uid)] # check we have not automatically authorized devices for remote, uid in remotes: self.assertEqual(remote.status, BoltDevice.CONNECTED) self.assertEqual(remote.stored, False) for remote, uid in remotes: with self.assertRaises(GLib.GError) as cm: client.enroll(uid) self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) # enable the authorization again now with client.record() as tape: client.auth_mode = 'enabled' tape.wait_for_props(AuthMode='enabled') self.assertEqual(client.auth_mode, 'enabled') policy = BoltClient.POLICY_DEFAULT for remote, uid in remotes: with remote.record() as tape: client.enroll(uid, policy) tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, client.default_policy) now = int(time.time()) self.assertEqual(remote.stored, True) self.assertTrue(remote.StoreTime > 1) self.assertTrue(remote.StoreTime >= before) self.assertTrue(remote.StoreTime <= now) def test_domain_basic(self): def make_uid(domain): return '884c6edd-7118-4b21-b186-b02d396ecca%d' % domain def make_domain(domain, security): name = 'domain%d' % domain uid = make_uid(domain) dom, host = self.add_domain_host(domain, security=security, uid=uid) return name, dom, host # check we get a proper event if the security level changes self.daemon_start() self.assertEqual(self.client.security_level, 'unknown') with self.client.record() as tape: d, dom, host = make_domain(0, 'user') tape.wait_for_props(SecurityLevel='user') self.assertEqual(self.client.security_level, 'user') self.daemon_stop() self.remove_domain_host(dom, host) # create more then one domain security = 'secure' domains = [make_domain(i, security) for i in range(4)] self.daemon_start() remotes = self.client.list_domains() self.assertEqual(len(domains), len(remotes)) for name, domain, host in domains: for remote in remotes: self.assertEqual(remote.security_level, security) remote = self.client.domain_by_id(name) self.assertEqual(remote.id, name) with self.assertRaises(GLib.GError): self.client.domain_by_id("") with self.assertRaises(GLib.GError): self.client.domain_by_id("nonexistent") for name, domain, host in domains: remote = self.client.domain_by_id(name) self.assertIsNotNone(remote) with remote.record() as tape: self.remove_domain_host(domain, host) tape.wait_for_props(SysfsPath='') for d in range(4): remote = self.client.domain_by_id(make_uid(d)) self.assertIsNotNone(remote) with remote.record() as tape: name, domain, host = make_domain(d, 'secure') tape.wait_for_props(SysfsPath=domain) remotes = self.client.list_domains() self.assertEqual(len(remotes), 4) self.daemon_stop() def test_domain_connected(self): uid = '884c6edd-7118-4b21-b186-b02d396ecafe' entry = ( "[domain]\n" "bootacl=\n" ) path = os.path.join(self.dbpath, 'domains', uid) with open(path, 'w') as f: f.write(entry) self.daemon_start() self.assertEqual(self.client.security_level, 'unknown') with self.client.record() as tape: security = 'secure' self.add_domain_host(0, security=security, uid=uid) tape.wait_for_props(SecurityLevel=security) self.assertEqual(self.client.security_level, security) self.daemon_stop() def test_domain_bootacl(self): ssd1 = TbDevice('SSD1',) cable1 = TbDevice('Cable1', children=[ssd1]) ssd2 = TbDevice('SSD2') cable2 = TbDevice('Cable2', children=[ssd2]) host = TbHost([cable1, cable2]) tree = TbDomain(security=TbDomain.SECURITY_SECURE, acl=9, host=host) tree.connect_tree(self.testbed) self.store_put_device(cable1, key=None) self.store_put_device(ssd1, key='known') self.store_put_device(cable2, key='known') self.store_put_device(ssd2, key='known') domain_id = host.unique_id self.daemon_start() self.polkitd_start() client = self.client domain = client.domain_by_id(domain_id) bootacl = domain.bootacl print('domain [%s] bootacl: %s' % (domain.uid, bootacl)) self.assertNotIn(host.unique_id, bootacl) devs = tree.peripherals for d in devs: self.assertIn(d.unique_id, bootacl) # try to set the bootacl, without PolKit allowing it with self.assertRaises(GLib.GError) as cm: domain.bootacl = [''] self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.manage']) # try to set an invalid bootacl with self.assertRaises(GLib.GError) as cm: domain.bootacl = [''] self.assertGError(cm, Gio.IOErrorEnum.INVALID_ARGUMENT) # try to set an invalid bootacl, 2nd time with self.assertRaises(GLib.GError) as cm: domain.bootacl = None self.assertGError(cm, Gio.IOErrorEnum.INVALID_ARGUMENT) # remove all entries with domain.record() as tape: domain.bootacl = ['']*len(bootacl) tape.wait_for_props(BootACL=None) bootacl = domain.bootacl print('domain [%s] bootacl: %s' % (domain.uid, bootacl)) self.assertTrue(all([x == '' for x in bootacl])) bootacl_new = ['']*len(bootacl) uuids = ["884c6edd-7118-4b21-b186-b02d396ecca0", "58796843-4a8b-4578-921a-acc654984bfb", "0cc8556f-c73b-41c0-990e-f1e317360626"] for i, u in enumerate(uuids): bootacl_new[i] = u with domain.record() as tape: domain.bootacl = bootacl_new tape.wait_for_props(BootACL=None) bootacl = domain.bootacl print('domain [%s] bootacl: %s' % (domain.uid, bootacl)) for entry in uuids: self.assertIn(entry, uuids) self.daemon_stop() def test_domain_integrated_tbt(self): # On integrated TBT (like ICL/TGL) the UUID of the controller is not # stable, i.e. it changes between reboots. We therefore must not be # saving the domain def make_events(name, devices): paths = [GLib.Variant("(o)", (dev.object_path, )) for dev in devices] return [Recorder.Event('signal', name, path, None) for path in paths] ice_lake = { "security": 'none', "bootacl":None, "iommu": "1", "nhi": 0x8a17, # ice lake } dom, host = self.add_domain_host(**ice_lake) self.daemon_start() client = self.client domains = client.list_domains() for domain in domains: self.assertFalse(self.store_has_domain(domain.uid)) with self.client.record() as tape: events = make_events('DomainRemoved', domains) self.remove_domain_host(dom, host) res = tape.wait_for_events(events) self.assertTrue(res) self.daemon_stop() def test_domain_cleanup_stale(self): stale = [] for _ in range(4): uid = self.store_create_domain() stale += [uid] uid = self.store_create_domain() acls = [{"uid": str(uuid.uuid4), "op": "+"}] self.store_create_journal("bootacl", uid, acls) self.daemon_start() client = self.client domains = client.list_domains() have = [] for domain in domains: self.assertNotIn(domain.uid, stale) have += [domain.uid] self.assertIn(uid, have) self.daemon_stop() def test_signals_on_start(self): # Check that we get DeviceAdded signals for un-authorized # devices that are not in the database client = BoltClient(self.dbus) tree = self.default_mock_tree() tree.connect_tree(self.testbed) with client.record() as tape: self.daemon_start() res = tape.wait_for_event('signal', 'DeviceAdded', None) self.assertTrue(res) self.daemon_stop() def test_basic_device_name(self): # prepare the basic setup _, host = self.add_domain_host() # pylint: disable=bad-whitespace devs = [ # name vendor label notes ['GNOME.org Cable', 'GNOME.org', 'GNOME.org Cable' ''], # duplicated vendor name ['GNOME.org Cable', 'GNOME.org', 'GNOME.org Cable #2' ''], # duplicated device ['GNOME.org Cable', 'GNOME.org', 'GNOME.org Cable #3' ''], # duplicated device, again ['⍾ Laptop', 'Evil Corp. ☢', 'Evil Corp. ☢ ⍾ Laptop'], # utf-8 chars ['HP TB3 Dock', 'HP Inc.', 'HP TB3 Dock' ''], # cleanup company names ['TB Gadget', 'Apple, Inc.', 'Apple TB Gadget' ''], # cleanup company names ] # pylint: enable=bad-whitespace devs = [{'name': d[0], 'vendor': d[1], 'label': d[2], 'id': i+1} for i, d in enumerate(devs)] for d in devs: did, name, vendor = d['id'], d['name'], d['vendor'] path, uid = self.add_device(host, did, name, vendor) d['path'] = path d['uid'] = uid self.daemon_start() devices = self.client.list_devices() self.assertEqual(len(devices), len(devs) + 1) for d in devs: remote = self.find_device_by_uid(devices, d['uid']) self.assertEqual(remote.name, d['name']) self.assertEqual(remote.vendor, d['vendor']) self.assertEqual(remote.label, d['label']) self.daemon_stop() def test_basic_user_config(self): self.user_config(DefaultPolicy='manual') self.daemon_start() policy = self.client.default_policy self.assertEqual(policy, self.client.POLICY_MANUAL) self.daemon_stop() def test_device_by_uid(self): self.daemon_start() with self.assertRaises(GLib.GError): self.client.device_by_uid("") with self.assertRaises(GLib.GError): self.client.device_by_uid("nonexistent") tree = self.default_mock_tree() tree.connect_tree(self.testbed) for d in tree.devices: remote = self.client.device_by_uid(d.unique_id) self.assertIsNotNone(remote) self.assertDeviceEqual(d, remote) self.daemon_stop() def test_device_authflags(self): key = self.key dc, host = self.add_domain_host(security='dponly') d1, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=1, key=None) d2, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=1, key=None) self.daemon_start() devices = self.client.list_devices() self.assertEqual(len(devices), 3) device = self.find_device_by_uid(devices, d1_uid) self.assertEqual(device.authflags, BoltDevice.NOPCIE) device = self.find_device_by_uid(devices, d2_uid) self.assertEqual(device.authflags, BoltDevice.NOPCIE) self.daemon_stop() self.testbed.set_attribute(dc, 'security', 'usbonly') self.daemon_start() devices = self.client.list_devices() self.assertEqual(len(devices), 3) device = self.find_device_by_uid(devices, d1_uid) self.assertEqual(device.authflags, BoltDevice.NOPCIE) device = self.find_device_by_uid(devices, d2_uid) self.assertEqual(device.authflags, BoltDevice.NOPCIE) self.daemon_stop() # secure mode self.testbed.remove_device(d1) self.testbed.remove_device(d2) self.testbed.set_attribute(dc, 'security', 'secure') d1, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=2, key=key, boot='0') d2, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=1, key=None, boot='1') self.daemon_start() devices = self.client.list_devices() self.assertEqual(len(devices), 3) device = self.find_device_by_uid(devices, d1_uid) flags = BoltDevice.SECURE self.assertEqual(device.authflags, flags) device = self.find_device_by_uid(devices, d2_uid) flags = BoltDevice.NOKEY | BoltDevice.BOOT self.assertEqual(device.authflags, flags) self.daemon_stop() def test_device_authorize(self): self.daemon_start() client = self.client tree = self.default_mock_tree() tree.connect_tree(self.testbed) self.polkitd_start() to_authorize = tree.collect(TbDevice.is_unauthorized) # check that we are not allowed to authorize devices for d in to_authorize: remote = self.client.device_by_uid(d.unique_id) with self.assertRaises(GLib.GError) as cm: remote.authorize() self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.authorize']) before = int(time.time()) for d in to_authorize: remote = self.client.device_by_uid(d.unique_id) tape = remote.record() remote.authorize() d.reload_auth() # will emit the uevent, so the daemon can update tape.wait_for_props(Status='authorized') self.assertDeviceEqual(d, remote) # make sure AuthorizeTime is correct now = int(time.time()) self.assertTrue(remote.AuthorizeTime > 1) self.assertTrue(remote.AuthorizeTime >= before) self.assertTrue(remote.AuthorizeTime <= now) tape.close() for d in to_authorize: remote = self.client.device_by_uid(d.unique_id) with self.assertRaises(GLib.GError) as cm: remote.authorize() # enroll the device, disconnect it and then try # try to authorize it, verifying we are rejecting # the call properly self.polkitd.SetAllowed(['org.freedesktop.bolt.authorize', 'org.freedesktop.bolt.enroll']) d = to_authorize[0] d.writekey('a26d5ad55b011df39ae06cae1fd329babfecac3465fe0a8828d6178f88e59083') remote = self.client.device_by_uid(d.unique_id) with remote.record() as tape: client.enroll(d.unique_id, 'manual') tape.wait_for_props(Stored=True) d.disconnect(self.testbed) tape.wait_for_props(Status='disconnected') err = None with self.assertRaises(GLib.GError) as cm: remote.authorize() err = cm.exception self.assertIn('bolt.Error.BadState', err.message) self.daemon_stop() def test_device_auto_auth_sl2(self): ssd1 = TbDevice('SSD1',) cable1 = TbDevice('Cable1', children=[ssd1]) ssd2 = TbDevice('SSD2') cable2 = TbDevice('Cable2', children=[ssd2]) dock1 = TbDevice('Dock1') cable3 = TbDevice('Cable3', children=[dock1]) dock2 = TbDevice('Dock2') cable4 = TbDevice('Cable4', children=[dock2]) tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=TbHost([ cable1, cable2, cable3, cable4, ])) tree.connect_tree(self.testbed) self.store_put_device(cable1, key=None) self.store_put_device(ssd1, key='known') self.store_put_device(cable2, key='known') self.store_put_device(ssd2, key='known') self.store_put_device(cable3, key='known', policy='manual') self.store_put_device(dock1, key='known') self.store_put_device(cable4, key='known', policy='iommu') self.store_put_device(dock2, key=None, policy='iommu') self.daemon_start() devices = self.client.list_devices() self.assertEqual(len(devices), len(tree.devices)) remote_ssd2 = self.find_device_by_uid(devices, ssd2.unique_id) self.assertIsNotNone(remote_ssd2) tape = remote_ssd2.record() with remote_ssd2.record() as tape: if remote_ssd2.status != BoltDevice.AUTHORIZED: tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd2.status, BoltDevice.AUTHORIZED) # cable1 does *NOT* have a key but we are in SECURE mode # so it should not be authorized remote_c1 = self.find_device_by_uid(devices, cable1.unique_id) self.assertEqual(remote_c1.status, BoltDevice.CONNECTED) remote_ssd1 = self.find_device_by_uid(devices, ssd1.unique_id) self.assertEqual(remote_ssd1.status, BoltDevice.CONNECTED) # cable3 has MANUAL policy and therefore should *NOT* be authorized remote_c3 = self.find_device_by_uid(devices, cable3.unique_id) self.assertEqual(remote_c3.status, BoltDevice.CONNECTED) remote_dock1 = self.find_device_by_uid(devices, dock1.unique_id) self.assertEqual(remote_dock1.status, BoltDevice.CONNECTED) # cable4 has IOMMU policy and therefore should *NOT* be authorized remote_c3 = self.find_device_by_uid(devices, cable3.unique_id) self.assertEqual(remote_c3.status, BoltDevice.CONNECTED) remote_dock1 = self.find_device_by_uid(devices, dock1.unique_id) self.assertEqual(remote_dock1.status, BoltDevice.CONNECTED) # now we pretend the user has authorized the device manually, # to check if boltd picks up udev changes properly and then # auto-authorizes also SSD1 # we start to tape recorder for ssd1 too, so we don't miss its events tape_ssd1 = remote_ssd1.record() with remote_c1.record() as tape: cable1.authorize('1') tape.wait_for_props(Status='authorized') self.assertEqual(remote_c1.status, BoltDevice.AUTHORIZED) tape_ssd1.wait_for_props(Status='authorized') events = tape_ssd1.close() self.assertTrue(Recorder.events_list_contains(events, 'property', 'Status', 'authorizing')) self.assertEqual(remote_ssd1.status, BoltDevice.AUTHORIZED) self.daemon_stop() def test_device_auto_auth_iommu(self): ssd1 = TbDevice('SSD1',) cable1 = TbDevice('Cable1', children=[ssd1]) ssd2 = TbDevice('SSD2') cable2 = TbDevice('Cable2', children=[ssd2]) tree = TbDomain(security=TbDomain.SECURITY_SECURE, iommu='1', host=TbHost([ cable1, cable2, ])) tree.connect_tree(self.testbed) self.store_put_device(cable1, key=None, policy='iommu') self.store_put_device(ssd1, key='known', policy='iommu') self.store_put_device(cable2, key='known', policy='iommu') self.store_put_device(ssd2, key='known', policy='auto') self.daemon_start() devices = self.client.list_devices() self.assertEqual(len(devices), len(tree.devices)) remote_ssd2 = self.find_device_by_uid(devices, ssd2.unique_id) self.assertIsNotNone(remote_ssd2) tape = remote_ssd2.record() with remote_ssd2.record() as tape: if remote_ssd2.status != BoltDevice.AUTHORIZED: tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd2.status, BoltDevice.AUTHORIZED) # cable1 does *NOT* have a key but we are in SECURE mode # so it should not be authorized even though IOMMU is one remote_c1 = self.find_device_by_uid(devices, cable1.unique_id) self.assertEqual(remote_c1.status, BoltDevice.CONNECTED) remote_ssd1 = self.find_device_by_uid(devices, ssd1.unique_id) self.assertEqual(remote_ssd1.status, BoltDevice.CONNECTED) self.daemon_stop() def device_import_test(self, security, devs, iommu): _, host = self.add_domain_host(security=security, iommu=iommu) for i, d in enumerate(devs): did = i + 1 path, uid = self.add_device(host, did, "Dock%d" % did, "GNOME.org", authorized=d['authorized'], key=d['key'], boot='%d' % d['boot']) d['path'] = path d['uid'] = uid self.daemon_start() self.polkitd_start() client = self.client devices = client.list_devices() self.assertEqual(len(devices), len(devs) + 1) for d in devs: remote = self.find_device_by_uid(devices, d['uid']) if d['authorized'] > 0: status = BoltDevice.AUTHORIZED else: status = BoltDevice.CONNECTED self.assertEqual(remote.status, status) policy = d['policy'] if policy is not None: stored = True self.assertEqual(remote.policy, policy) else: stored = False self.assertEqual(remote.stored, stored) self.daemon_stop() @Parameterize.make(iommu='1') def test_device_import_sl0(self, iommu=None): # no security, kinda game over, because the device already has # full access now devs = [ {'authorized': 1, 'key': None, 'boot': 0, 'policy': 'iommu'}, {'authorized': 1, 'key': None, 'boot': 1, 'policy': 'iommu'}, ] self.device_import_test('none', devs, iommu) @Parameterize.make(iommu='1') def test_device_import_sl1(self, iommu=None): policy = 'iommu' if iommu else 'auto' devs = [ # no boot flag -> not importing {'authorized': 1, 'key': None, 'boot': 0, 'policy': None}, # boot flag, and user mode -> importing {'authorized': 1, 'key': None, 'boot': 1, 'policy': policy}, ] if not iommu: # pylint: disable=bad-whitespace devs += [ # check we are not auto-importing un-authorized devices {'authorized': 0, 'key': None, 'boot': 0, 'policy': None}, {'authorized': 0, 'key': '', 'boot': 0, 'policy': None}, ] # pylint: enable=bad-whitespace self.device_import_test('user', devs, iommu) @Parameterize.make(iommu='1') def test_device_import_sl2(self, iommu=None): # like test_device_auto_import but in SECURE mode key = self.key # pylint: disable=bad-whitespace devs = [ # no boot flag, not importing {'authorized': 1, 'key': None, 'boot': 0, 'policy': None}, {'authorized': 2, 'key': key, 'boot': 0, 'policy': None}, # boot flag present but no valid key, not importing {'authorized': 1, 'key': None, 'boot': 1, 'policy': 'iommu'}, {'authorized': 1, 'key': '', 'boot': 1, 'policy': 'iommu'}, # boot, valid key, should not be possible to observe "in the wild" {'authorized': 2, 'key': key, 'boot': 1, 'policy': 'iommu'}, ] if not iommu: devs += [ # check we are not auto-importing un-authorized devices {'authorized': 0, 'key': None, 'boot': 0, 'policy': None}, {'authorized': 0, 'key': '', 'boot': 0, 'policy': None}, ] # pylint: enable=bad-whitespace self.device_import_test('secure', devs, iommu) @Parameterize.make(iommu='1') def test_device_import_sl3(self, iommu=None): # auto import test for dponly mode, i.e no pci express tunnels # in that mode all devices will always be marked as authorized devs = [ {'authorized': 1, 'key': None, 'boot': 0, 'policy': None}, {'authorized': 1, 'key': None, 'boot': 1, 'policy': None}, ] self.device_import_test('dponly', devs, iommu) def test_device_auto_enroll(self): # the negative tests, i.e. that devices are not auto-enrolled when iommu is # disabled is done via test_basic and test_device_auto_import_{sl1, sl2} tree = TbDomain(host=TbHost([ TbDevice('Cable1', children=[ TbDevice('Dock1') ]), TbDevice('Cable2', children=[ TbDevice('SSD1', key=None) ]), ]), acl=8, iommu='1') tree.connect_tree(self.testbed) devs = tree.collect(TbDevice.is_unauthorized) self.daemon_start() self.polkitd_start() client = self.client devices = client.list_devices() self.assertEqual(len(devices), len(devs) + 1) # devices closer to the root first for d in sorted(devs, key=lambda d: len(d.syspath)): remote = self.find_device_by_uid(devices, d.unique_id) self.assertIsNotNone(remote) with remote.record() as tape: if remote.status != BoltDevice.AUTHORIZED: tape.wait_for_props(Stored=True) self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, True) self.daemon_stop() def test_device_enroll(self): self.daemon_start() tree = self.default_mock_tree(acl=16) tree.connect_tree(self.testbed) self.polkitd_start() client = self.client domain = client.domain_by_id(tree.unique_id) self.assertIsNotNone(domain) self.assertIsNotNone(domain.bootacl) to_enroll = tree.collect(TbDevice.is_unauthorized) self.assertNotEqual(to_enroll, 0) # check that we are not allowed to enroll devices, i.e. the correct # policykit action is called. for d in to_enroll: with self.assertRaises(GLib.GError) as cm: client.enroll(d.unique_id) self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.enroll']) # check we get a proper error for a unknown device with self.assertRaises(GLib.GError) as cm: # non-existent uuid client.forget("884c6edd-7118-4b21-b186-b02d396ecca0") before = int(time.time()) policies = {} for i, d in enumerate(to_enroll): if i % 2 == 0: policy = BoltClient.POLICY_AUTO with domain.record() as tape: remote = client.enroll(d.unique_id, policy) tape.wait_for_props(BootACL=None) else: policy = BoltClient.POLICY_MANUAL remote = client.enroll(d.unique_id, policy) d.reload_auth() # will emit the uevent, so the daemon can update # the security level for the domain is SECURE, which means we should # have authorized via a new key: # status should be AUTHORIZED # stored should be True # key(state) should be KEY_NEW # authflags should be 'secure' self.assertDeviceEqual(d, remote) self.assertTrue(remote.stored, True) self.assertEqual(remote.key, BoltDevice.KEY_NEW) self.assertEqual(remote.policy, policy) # check the StoreTime is correct now = int(time.time()) self.assertEqual(remote.stored, True) self.assertTrue(remote.StoreTime > 1) self.assertTrue(remote.StoreTime >= before) self.assertTrue(remote.StoreTime <= now) self.assertTrue(remote.AuthorizeTime > 1) self.assertTrue(remote.AuthorizeTime >= before) self.assertTrue(remote.AuthorizeTime <= now) if policy == BoltClient.POLICY_AUTO: self.assertIn(d.unique_id, domain.bootacl) else: self.assertNotIn(d.unique_id, domain.bootacl) policies[d.unique_id] = policy # we disconnect the tree, but since the devices are connected # the daemon should have them in its database now tree.disconnect(self.testbed) expected_number = len(tree.peripherals) devices = self.client.list_peripherals() tries = 0 while expected_number != len(devices) and tries < 3: time.sleep(.2) tries += 1 devices = self.client.list_peripherals() self.assertEqual(len(devices), expected_number) tree.connect(self.testbed) # we connect the domain again tree.children[0].connect(self.testbed) # and the host too for i, remote in enumerate(devices): policy = policies[remote.uid] local = tree.find(False, unique_id=remote.uid) self.assertDeviceEqual(local, remote) self.assertTrue(remote.stored, True) # key status should have changed to HAVE from NEW self.assertEqual(remote.key, BoltDevice.KEY_HAVE) self.assertEqual(remote.policy, policy) # now we connect that specific device and wait for # the property changes with remote.record() as tape: local.connect(self.testbed) if policy == BoltClient.POLICY_AUTO: status = 'authorized' else: status = 'connected' tape.wait_for_props(Status=status) local.reload_auth() # will emit the uevent, so the daemon can update self.assertDeviceEqual(local, remote) def test_enroll_authorized(self): key = self.key _, host = self.add_domain_host() _, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=2, key=key, boot='0') _, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=1, key=None, boot='0') self.daemon_start() self.polkitd_start() client = self.client devices = self.client.list_devices() self.assertEqual(len(devices), 3) self.polkitd.SetAllowed(['org.freedesktop.bolt.enroll']) d1_remote = self.find_device_by_uid(devices, d1_uid) d2_remote = self.find_device_by_uid(devices, d2_uid) before = int(time.time()) for remote in [d1_remote, d2_remote]: self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, False) policy = BoltClient.POLICY_DEFAULT for remote, uid in [(d1_remote, d1_uid), (d2_remote, d2_uid)]: with remote.record() as tape: client.enroll(uid, policy) tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, client.default_policy) now = int(time.time()) self.assertEqual(remote.stored, True) self.assertTrue(remote.StoreTime > 1) self.assertTrue(remote.StoreTime >= before) self.assertTrue(remote.StoreTime <= now) self.daemon_stop() def test_enroll_iommu(self): _, host = self.add_domain_host(security='secure', iommu='1') self.daemon_start() self.polkitd_start() client = self.client self.polkitd.SetAllowed(['org.freedesktop.bolt.manage', 'org.freedesktop.bolt.enroll', 'org.freedesktop.bolt.authorize']) # disable the authorization globally with client.record() as tape: client.auth_mode = 'disabled' tape.wait_for_props(AuthMode='disabled') self.assertEqual(client.auth_mode, 'disabled') _, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=0, key='', boot='0') _, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=0, key=None, boot='0') devices = self.client.list_devices() self.assertEqual(len(devices), 3) d1_remote = self.find_device_by_uid(devices, d1_uid) d2_remote = self.find_device_by_uid(devices, d2_uid) before = int(time.time()) remotes = [(d1_remote, d1_uid), (d2_remote, d2_uid)] # check we have not automatically enrolled/authorized devices for remote, uid in remotes: self.assertEqual(remote.status, BoltDevice.CONNECTED) self.assertEqual(remote.stored, False) # enable the authorization again now with client.record() as tape: client.auth_mode = 'enabled' tape.wait_for_props(AuthMode='enabled') self.assertEqual(client.auth_mode, 'enabled') # with iommu enabled, the policy should be adjusted to 'IOMMU' policy = BoltClient.POLICY_DEFAULT for remote, uid in remotes: with remote.record() as tape: client.enroll(uid, policy) tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, client.POLICY_IOMMU) now = int(time.time()) self.assertEqual(remote.stored, True) self.assertTrue(remote.StoreTime > 1) self.assertTrue(remote.StoreTime >= before) self.assertTrue(remote.StoreTime <= now) def test_device_key_upgrade(self): dock = TbDevice('Dock',) ssd1 = TbDevice('SSD1',) ssd2 = TbDevice('SSD2',) ssd2.key = None tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=TbHost([ dock, ssd1, ssd2 ])) tree.connect_tree(self.testbed) self.store_put_device(dock) self.store_put_device(ssd2) self.daemon_start() self.polkitd_start() devices = self.client.list_devices() self.assertEqual(len(devices), len(tree.devices)) self.polkitd.SetAllowed(['org.freedesktop.bolt.authorize']) remote_dock = self.find_device_by_uid(devices, dock.unique_id) remote_ssd1 = self.find_device_by_uid(devices, ssd1.unique_id) remote_ssd2 = self.find_device_by_uid(devices, ssd2.unique_id) self.assertEqual(remote_dock.key, BoltDevice.KEY_MISSING) self.assertEqual(remote_ssd1.key, BoltDevice.KEY_MISSING) self.assertEqual(remote_ssd2.key, BoltDevice.KEY_MISSING) with remote_dock.record() as tape: remote_dock.authorize() tape.wait_for_props(Key='new') self.assertEqual(remote_dock.key, BoltDevice.KEY_NEW) with remote_ssd1.record() as tape: remote_ssd1.authorize() tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd1.key, BoltDevice.KEY_MISSING) with remote_ssd2.record() as tape: remote_ssd2.authorize() tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd2.key, BoltDevice.KEY_MISSING) def test_device_forget(self): self.daemon_start() tree = self.default_mock_tree(acl=16) self.polkitd_start() tree.connect_tree(self.testbed) client = self.client self.polkitd.SetAllowed(['org.freedesktop.bolt.enroll']) to_enroll = tree.collect(TbDevice.is_unauthorized) policy = BoltClient.POLICY_AUTO for d in to_enroll: remote = client.enroll(d.unique_id, policy) d.reload_auth() self.assertDeviceEqual(d, remote) self.assertTrue(remote.stored, True) self.assertEqual(remote.key, BoltDevice.KEY_NEW) self.assertEqual(remote.policy, policy) tree.disconnect(self.testbed) expected_number = len(tree.peripherals) devices = self.client.list_peripherals() tries = 0 while expected_number != len(devices) and tries < 3: time.sleep(.2) tries += 1 devices = self.client.list_peripherals() self.assertEqual(len(devices), expected_number) for remote in devices: with self.assertRaises(GLib.GError) as cm: client.forget(remote.uid) self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.manage']) # check we get a proper error for a unknown device with self.assertRaises(GLib.GError) as cm: # non-existent uuid client.forget("884c6edd-7118-4b21-b186-b02d396ecca0") domain = client.domain_by_id(tree.unique_id) # now we actually forget the device for remote in devices: self.assertIn(remote.uid, domain.bootacl) with domain.record() as tape: client.forget(remote.uid) tape.wait_for_props(BootACL=None) self.assertNotIn(remote.uid, domain.bootacl) devices = self.client.list_peripherals() self.assertEqual(len(devices), 0) def test_device_label(self): self.daemon_start() tree = self.simple_mock_tree() self.polkitd_start() tree.connect_tree(self.testbed) client = self.client self.polkitd.SetAllowed(['org.freedesktop.bolt.enroll']) local = tree.collect(TbDevice.is_unauthorized)[0] policy = BoltClient.POLICY_AUTO remote = client.enroll(local.unique_id, policy) local.reload_auth() self.assertEqual(remote.label, "%s %s" % (local.vendor_name, local.device_name)) self.assertDeviceEqual(local, remote) self.assertTrue(remote.stored, True) self.assertEqual(remote.key, BoltDevice.KEY_NEW) self.assertEqual(remote.policy, policy) with self.assertRaises(GLib.GError) as cm: remote.label = 'not authorized' self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.manage']) for val in ['', ' ', ' ']: with self.assertRaises(GLib.GError) as cm: remote.label = val self.assertGError(cm, Gio.DBusError.INVALID_ARGS) self.assertEqual(remote.label, "%s %s" % (local.vendor_name, local.device_name)) # store update failure check is done in test_device_store_failures val = 'A valid label' with remote.record() as tape: remote.label = val tape.wait_for_props(Label=val) self.assertEqual(remote.label, val) self.daemon_stop() @unittest.skipIf(can_override_dac(), "have DAC override") def test_device_store_failures(self): self.daemon_start() tree = self.simple_mock_tree() self.polkitd_start() tree.connect_tree(self.testbed) client = self.client self.polkitd.SetAllowed(['org.freedesktop.bolt.enroll', 'org.freedesktop.bolt.manage']) local = tree.collect(TbDevice.is_unauthorized)[0] policy = BoltClient.POLICY_AUTO remote = client.enroll(local.unique_id, policy) local.reload_auth() label = remote.label # check for store errors and verify the label did not change with self.assertRaises(GLib.GError) as cm, \ self.store_deny_device(local.unique_id): remote.label = 'denied' self.assertGError(cm, Gio.IOErrorEnum.PERMISSION_DENIED) self.assertEqual(remote.label, label) # check store update failures with self.assertRaises(GLib.GError) as cm, \ self.store_deny_device(local.unique_id): remote.policy = 'iommu' self.assertGError(cm, Gio.IOErrorEnum.PERMISSION_DENIED) self.assertEqual(remote.policy, policy) self.daemon_stop() def test_device_policy(self): dock = TbDevice('Dock', gen=3) tree = TbDomain(security=TbDomain.SECURITY_SECURE, acl=16, host=TbHost([dock])) tree.connect_tree(self.testbed) # multiple domain controller for bootacl checks ssd = TbDevice('SSD', gen=4) dom2 = TbDomain(security=TbDomain.SECURITY_SECURE, index=1, acl=16, host=TbHost([ssd], name="Laptop1")) dom2.connect_tree(self.testbed) self.daemon_start() self.polkitd_start() client = self.client remote = client.device_by_uid(dock.unique_id) # we are not allowed to manage the device with self.assertRaises(GLib.GError) as cm: remote.policy = 'iommu' self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.manage', 'org.freedesktop.bolt.enroll']) # device is not stored with self.assertRaises(GLib.GError) as cm: remote.policy = 'iommu' self.assertGError(cm, Gio.DBusError.INVALID_ARGS) # enroll the device with manual policy policy = BoltClient.POLICY_MANUAL with remote.record() as tape: remote = client.enroll(dock.unique_id, policy) dock.reload_auth() tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, BoltClient.POLICY_MANUAL) # policy is invalid with self.assertRaises(GLib.GError) as cm: remote.policy = 'foobar' self.assertGError(cm, Gio.DBusError.INVALID_ARGS) # store update failure check is done in test_device_store_failures # finally a valid update with remote.record() as tape: remote.policy = 'iommu' tape.wait_for_props(Policy='iommu') self.assertEqual(remote.policy, BoltClient.POLICY_IOMMU) # enroll the ssd with manual policy remote = client.device_by_uid(ssd.unique_id) policy = BoltClient.POLICY_MANUAL with remote.record() as tape: remote = client.enroll(ssd.unique_id, policy) ssd.reload_auth() tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, BoltClient.POLICY_MANUAL) # policy -> auto, check that it got added to the bootacl with remote.record() as tape: remote.policy = 'auto' tape.wait_for_props(Policy='auto') self.assertEqual(remote.policy, BoltClient.POLICY_AUTO) for dom in [tree, dom2]: domain = self.client.domain_by_id(dom.unique_id) self.assertIn(ssd.unique_id, domain.bootacl) # policy -> manual, check it got removed from the bootacl with remote.record() as tape: remote.policy = 'auto' tape.wait_for_props(Policy='auto') self.assertEqual(remote.policy, BoltClient.POLICY_AUTO) for dom in [tree, dom2]: domain = self.client.domain_by_id(dom.unique_id) self.assertIn(ssd.unique_id, domain.bootacl) def test_device_generation(self): dock = TbDevice('Dock', gen=3) ssd1 = TbDevice('SSD1', gen=1) ssd2 = TbDevice('SSD2', gen=2) ssd2 = TbDevice('Ethernet', gen=None) tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=TbHost([ dock, ssd1, ssd2 ], gen=4)) tree.connect_tree(self.testbed) self.daemon_start() for d in tree.devices: remote = self.client.device_by_uid(d.unique_id) self.assertDeviceEqual(d, remote) print("local: %d, remote: %d" % (d.generation or 0, remote.generation)) self.assertEqual(self.client.generation, 4) def test_device_generation_update(self): dock = TbDevice('Dock', gen=0) host = TbHost([dock], gen=0) tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=host) self.store_put_device(host) self.store_put_device(dock) self.daemon_start() self.assertEqual(self.client.generation, 0) remote = self.client.device_by_uid(host.unique_id) self.assertEqual(remote.device_type, BoltDevice.HOST) with self.client.record() as tape: host.generation = 4 dock.generation = 3 tree.connect_tree(self.testbed) tape.wait_for_props(Generation=4) self.daemon_stop() # ensure that the updates are written to the store stored = self.store_get_device(host.unique_id) self.assertEqual(stored.generation, 4) stored = self.store_get_device(dock.unique_id) self.assertEqual(stored.generation, 3) def test_device_linkspeed(self): dock = TbDevice('Dock', gen=1) host = TbHost([dock], gen=0) tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=host) linkspeed = {'rx.speed': 20, 'rx.lanes': 1, 'tx.speed': 10, 'tx.lanes': 2} dock.linkspeed = linkspeed tree.connect_tree(self.testbed) self.daemon_start() remote = self.client.device_by_uid(dock.unique_id) self.assertEqual(remote.linkspeed, linkspeed) with remote.record() as tape: linkspeed = {'rx.speed': 20, 'rx.lanes': 2, 'tx.speed': 10, 'tx.lanes': 1} dock.linkspeed = linkspeed tape.wait_for_props(LinkSpeed=linkspeed) self.daemon_stop() def test_sdnotify(self): self.add_domain_host(security='secure', iommu='1') self.daemon_start(sdnotify=True) self.polkitd_start() assert(self.sdnotify) msgs = self.sdnotify.wait_for('STATUS') self.assertTrue(msgs is not None) self.daemon_stop() def test_boltctl(self): ssd1 = TbDevice('SSD1',) cable1 = TbDevice('Cable1', children=[ssd1]) ssd2 = TbDevice('SSD2') cable2 = TbDevice('Cable2', children=[ssd2]) ssd3 = TbDevice('SSD3') cable3 = TbDevice('Cable3', children=[ssd3]) host = TbHost([cable1, cable2, cable3]) tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=host) tree.connect_tree(self.testbed) self.daemon_start() self.polkitd_start() out, _, res = self.boltctl('--version') out = str(out) self.assertEqual(res, 0) self.assertNotEqual(len(out), 0) self.assertIn('bolt', out) out, _, res = self.boltctl('power', '-q') out = str(out) self.assertEqual(res, 0) self.assertNotEqual(len(out), 0) self.assertIn('supported', out) out, _, res = self.boltctl('domains') out = str(out) self.assertEqual(res, 0) self.assertNotEqual(len(out), 0) self.assertIn(tree.unique_id, out) self.polkitd.SetAllowed(['org.freedesktop.bolt.authorize', 'org.freedesktop.bolt.enroll', 'org.freedesktop.bolt.manage']) to_enroll = [cable1, ssd1] out, _, res = self.boltctl('list') out = str(out) self.assertEqual(res, 0) for dev in to_enroll: self.assertIn(dev.unique_id, out) for dev in to_enroll: uid = dev.unique_id _, _, res = self.boltctl('authorize', uid) self.assertEqual(res, 0) remote = self.client.device_by_uid(uid) self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, False) for dev in to_enroll: uid = dev.unique_id _, _, res = self.boltctl('enroll', uid) self.assertEqual(res, 0) remote = self.client.device_by_uid(uid) self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, True) out, _, res = self.boltctl('info', uid) out = str(out) self.assertEqual(res, 0) self.assertIn('authorized', out) for dev in to_enroll: uid = dev.unique_id _, _, res = self.boltctl('forget', uid) self.assertEqual(res, 0) remote = self.client.device_by_uid(uid) self.assertEqual(remote.stored, False) for dev in to_enroll: uid = dev.unique_id _, _, res = self.boltctl('enroll', uid) self.assertEqual(res, 0) remote = self.client.device_by_uid(uid) self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, True) _, _, res = self.boltctl('forget', '--all') self.assertEqual(res, 0) for dev in to_enroll: uid = dev.unique_id remote = self.client.device_by_uid(uid) self.assertEqual(remote.stored, False) # chain enrollment _, _, res = self.boltctl('enroll', '--chain', ssd2.unique_id) self.assertEqual(res, 0) for dev in [cable2, ssd2]: uid = dev.unique_id remote = self.client.device_by_uid(uid) self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, True) # chain authorization _, _, res = self.boltctl('authorize', '--chain', ssd3.unique_id) self.assertEqual(res, 0) for dev in [cable3, ssd3]: uid = dev.unique_id remote = self.client.device_by_uid(uid) self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, False) # boltctl config: describe properties out, _, res = self.boltctl('config', '--describe') out = str(out) self.assertEqual(res, 0) self.assertNotEqual(len(out), 0) self.assertIn('auth-mode', out) self.assertIn('domain.bootacl', out) self.assertIn('device.label', out) out, _, res = self.boltctl('config', 'auth-mode') out = str(out) self.assertEqual(res, 0) self.assertNotEqual(len(out), 0) self.assertIn('enabled', out) with self.client.record() as tape: _, _, res = self.boltctl('config', 'auth-mode', 'disabled') self.assertEqual(res, 0) tape.wait_for_props(AuthMode='disabled') self.assertEqual(self.client.auth_mode, 'disabled') # boltctl config: set device name target = ssd2.unique_id remote = self.client.device_by_uid(target) with remote.record() as tape: _, _, res = self.boltctl('config', 'device.label', target, 'Nobody') self.assertEqual(res, 0) tape.wait_for_props(Label='Nobody') self.assertEqual(remote.label, 'Nobody') # all done self.daemon_stop() def list_tests(): suit = unittest.defaultTestLoader.loadTestsFromTestCase(BoltTest) for t in suit: comps = t.id().split('.') machine = ".".join(comps[1:]) human = comps[2][len('test_'):] yield machine, human def main(): if len(sys.argv) == 2 and sys.argv[1] == "list-tests": for machine, human in list_tests(): print("%s %s" % (machine, human), end="\n") sys.exit(0) if 'umockdev' not in os.environ.get('LD_PRELOAD', ''): wrapped = ['umockdev-wrapper'] + sys.argv os.execvp(wrapped[0], wrapped) unittest.main(verbosity=2) if __name__ == '__main__': main() bolt-0.9.2/tests/test-journal.c000066400000000000000000000337341417453051000164310ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-journal.h" #include "bolt-dbus.h" #include "bolt-fs.h" #include "bolt-test.h" #include #include #include #include #include #include #include typedef struct { char *path; GFile *root; } TestJournal; static void test_journal_setup (TestJournal *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; tt->path = g_dir_make_tmp ("bolt.journal.XXXXXX", &error); if (tt->path == NULL) { g_critical ("Could not create tmp dir: %s", error->message); return; } g_debug ("journal test path at: %s", tt->path); tt->root = g_file_new_for_path (tt->path); } static void test_journal_tear_down (TestJournal *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; gboolean ok; ok = bolt_fs_cleanup_dir (tt->path, &error); if (!ok) g_warning ("Could not clean up dir: %s", error->message); g_clear_object (&tt->root); g_clear_pointer (&tt->path, g_free); } GLogWriterOutput static nonfatal_logger (GLogLevelFlags log_level, const GLogField *fields, gsize n_fields, gpointer user_data) { return g_log_writer_standard_streams (log_level, fields, n_fields, user_data); } static void test_journal_object (TestJournal *tt, gconstpointer user_data) { g_autoptr(BoltJournal) j = NULL; g_autoptr(GError) err = NULL; g_autoptr(GFile) root = NULL; g_autofree char *name = NULL; gboolean fresh; if (g_test_subprocess ()) { g_autofree char *path = NULL; int r; /* we fiddle with the writer, and also want to check * stderr, so the easy way is to do from a subprocess */ g_log_set_writer_func (nonfatal_logger, NULL, NULL); /* name but no root */ j = g_initable_new (BOLT_TYPE_JOURNAL, NULL, &err, "name", "test", NULL); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_FAILED); g_assert_null (j); g_clear_pointer (&err, g_error_free); /* root but no name */ j = g_initable_new (BOLT_TYPE_JOURNAL, NULL, &err, "root", tt->root, NULL); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_FAILED); g_assert_null (j); g_clear_pointer (&err, g_error_free); path = g_build_filename (tt->path, "nobody", NULL); r = g_mkdir (path, 0755); g_assert_cmpint (r, >, -1); /* root, name, but name is an existing dir */ j = g_initable_new (BOLT_TYPE_JOURNAL, NULL, &err, "root", tt->root, "name", "nobody", NULL); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY); g_assert_null (j); g_clear_pointer (&err, g_error_free); return; } g_test_trap_subprocess (NULL, 0, 0); g_test_trap_assert_passed (); g_test_trap_assert_stderr ("*invalid*"); j = bolt_journal_new (tt->root, "test", &err); g_assert_no_error (err); g_assert_nonnull (j); g_object_get (j, "name", &name, "root", &root, "fresh", &fresh, NULL); g_assert_cmpstr (name, ==, "test"); g_assert_true (g_file_equal (root, tt->root)); g_assert_true (fresh); } static void test_journal_create (TestJournal *tt, gconstpointer user_data) { g_autoptr(BoltJournal) j = NULL; g_autoptr(GError) err = NULL; /* creation and freshness check */ j = bolt_journal_new (tt->root, "test", &err); g_assert_no_error (err); g_assert_nonnull (j); g_assert_true (bolt_journal_is_fresh (j)); g_clear_object (&j); j = bolt_journal_new (tt->root, "test", &err); g_assert_no_error (err); g_assert_nonnull (j); g_assert_true (bolt_journal_is_fresh (j)); } static void test_journal_insert (TestJournal *tt, gconstpointer user_data) { g_autoptr(BoltJournal) j = NULL; g_autoptr(GError) err = NULL; g_autoptr(GPtrArray) arr = NULL; gboolean ok; static BoltJournalItem items[] = { {(char *) "aaaa", BOLT_JOURNAL_ADDED, 0}, {(char *) "bbbb", BOLT_JOURNAL_REMOVED, 0}, {(char *) "cccc", BOLT_JOURNAL_REMOVED, 0}, {(char *) "dddd", BOLT_JOURNAL_ADDED, 0}, {NULL, BOLT_JOURNAL_FAILED, 0}, }; j = bolt_journal_new (tt->root, "test", &err); g_assert_no_error (err); g_assert_nonnull (j); arr = bolt_journal_list (j, &err); g_assert_cmpuint (arr->len, ==, 0); for (BoltJournalItem *i = items; i->id; i++) { ok = bolt_journal_put (j, i->id, i->op, &err); g_assert_no_error (err); g_assert_true (ok); } g_assert_false (bolt_journal_is_fresh (j)); g_clear_pointer (&arr, g_ptr_array_unref); arr = bolt_journal_list (j, &err); g_assert_no_error (err); g_assert_nonnull (arr); g_assert_cmpuint (arr->len, ==, G_N_ELEMENTS (items) - 1); for (guint i = 0; i < arr->len; i++) { BoltJournalItem *ours = items + i; BoltJournalItem *theirs = arr->pdata[i]; g_assert_cmpstr (theirs->id, ==, ours->id); g_assert_cmpint (theirs->op, ==, ours->op); } /* close and re-open and re-do the test */ g_clear_object (&j); g_clear_pointer (&arr, g_ptr_array_unref); j = bolt_journal_new (tt->root, "test", &err); g_assert_no_error (err); g_assert_nonnull (j); arr = bolt_journal_list (j, &err); g_assert_no_error (err); g_assert_nonnull (arr); g_assert_cmpuint (arr->len, ==, G_N_ELEMENTS (items) - 1); for (guint i = 0; i < arr->len; i++) { BoltJournalItem *ours = items + i; BoltJournalItem *theirs = arr->pdata[i]; g_assert_cmpstr (theirs->id, ==, ours->id); g_assert_cmpint (theirs->op, ==, ours->op); } /* reset the journal */ ok = bolt_journal_reset (j, &err); g_assert_no_error (err); g_assert_true (ok); g_clear_pointer (&arr, g_ptr_array_unref); arr = bolt_journal_list (j, &err); g_assert_cmpuint (arr->len, ==, 0); g_assert_true (bolt_journal_is_fresh (j)); } static gint sort_journal_items (gconstpointer a, gconstpointer b) { const BoltJournalItem *ia = *((BoltJournalItem **) a); const BoltJournalItem *ib = *((BoltJournalItem **) b); return g_strcmp0 (ia->id, ib->id); } static void test_journal_diff (TestJournal *tt, gconstpointer user_data) { g_autoptr(BoltJournal) j = NULL; g_autoptr(GError) err = NULL; g_autoptr(GPtrArray) arr = NULL; g_autoptr(GHashTable) diff = NULL; gboolean ok; static BoltJournalItem items[] = { {(char *) "aaaa", BOLT_JOURNAL_ADDED, 0}, {(char *) "bbbb", BOLT_JOURNAL_REMOVED, 0}, {(char *) "cccc", BOLT_JOURNAL_REMOVED, 0}, {(char *) "dddd", BOLT_JOURNAL_ADDED, 0}, {(char *) "eeee", BOLT_JOURNAL_ADDED, 0}, {(char *) "ffff", BOLT_JOURNAL_ADDED, 0}, }; guint k; /* bolt_journal_put_diff uses bolt_copy_bytes, which in turn uses * copy_file_range(2) internally, which was added in linux 4.5. */ skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), "linux kernel < 4.5, copy_file_range syscall missing"); j = bolt_journal_new (tt->root, "diff", &err); /* the first and the last elements are added "manually" * the rest via _put_diff */ ok = bolt_journal_put (j, items[0].id, items[0].op, &err); g_assert_no_error (err); g_assert_true (ok); diff = g_hash_table_new (g_str_hash, g_str_equal); for (k = 1; k < G_N_ELEMENTS (items) - 2; k++) { BoltJournalItem *i = items + k; int op = (i->op == BOLT_JOURNAL_ADDED) ? '+' : '-'; g_hash_table_insert (diff, i->id, GINT_TO_POINTER (op)); } ok = bolt_journal_put_diff (j, diff, &err); g_assert_no_error (err); g_assert_true (ok); /* the last element */ ok = bolt_journal_put (j, items[k].id, items[k].op, &err); g_assert_no_error (err); g_assert_true (ok); arr = bolt_journal_list (j, &err); g_assert_no_error (err); g_assert_nonnull (arr); g_assert_cmpuint (arr->len, ==, G_N_ELEMENTS (items) - 1); /* put_diff does not have an order of any of the items * within the diff, so they are sorted to the same order * as the initial array */ g_ptr_array_sort (arr, sort_journal_items); for (guint i = 0; i < arr->len; i++) { BoltJournalItem *ours = items + i; BoltJournalItem *theirs = g_ptr_array_index (arr, i); g_assert_cmpstr (theirs->id, ==, ours->id); g_assert_cmpint (theirs->op, ==, ours->op); } } static void test_journal_diff_fresh (TestJournal *tt, gconstpointer user_data) { g_autoptr(BoltJournal) j = NULL; g_autoptr(GError) err = NULL; g_autoptr(GPtrArray) arr = NULL; g_autoptr(GHashTable) diff = NULL; gboolean ok; static BoltJournalItem items[] = { {(char *) "aaaa", BOLT_JOURNAL_ADDED, 0}, {(char *) "bbbb", BOLT_JOURNAL_REMOVED, 0}, }; guint k; /* bolt_journal_put_diff uses bolt_copy_bytes, which in turn uses * copy_file_range(2) internally, which was added in linux 4.5. */ skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), "linux kernel < 4.5, copy_file_range syscall missing"); j = bolt_journal_new (tt->root, "diff_fresh", &err); g_assert_true (bolt_journal_is_fresh (j)); diff = g_hash_table_new (g_str_hash, g_str_equal); for (k = 1; k < G_N_ELEMENTS (items); k++) { BoltJournalItem *i = items + k; int op = (i->op == BOLT_JOURNAL_ADDED) ? '+' : '-'; g_hash_table_insert (diff, i->id, GINT_TO_POINTER (op)); } ok = bolt_journal_put_diff (j, diff, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (bolt_journal_is_fresh (j)); } static void test_journal_invalid_file (TestJournal *tt, gconstpointer user_data) { if (g_test_subprocess ()) { g_autoptr(GError) err = NULL; g_autofree char *path = NULL; gboolean ok; const char * const invalid_data[] = { "justonestring\n", "invalidop X 0XFF", "str str str\n", "str str\n", "\n", NULL, }; /* we fiddle with the writer, and also want to check * stderr, so the easy way is to do from a subprocess */ g_log_set_writer_func (nonfatal_logger, NULL, NULL); path = g_build_filename (tt->path, "bootacl", NULL); for (const char *const *i = invalid_data; *i; i++) { g_autoptr(BoltJournal) j = NULL; g_autoptr(GPtrArray) arr = NULL; const char *data = *i; ok = g_file_set_contents (path, data, -1, &err); g_assert_no_error (err); g_assert_true (ok); j = bolt_journal_new (tt->root, "bootacl", &err); arr = bolt_journal_list (j, &err); g_assert_no_error (err); g_assert_nonnull (arr); } exit (0); } g_test_trap_subprocess (NULL, 0, 0); g_test_trap_assert_passed (); g_test_trap_assert_stderr ("*invalid entry*"); } static void test_journal_op_stringops (TestJournal *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; BoltJournalOp op; char ops[] = "!=+-"; /* don't include the trailing \0 */ for (gsize i = 0; i < G_N_ELEMENTS (ops) - 1; i++) { g_autoptr(GError) err = NULL; char str[] = {ops[i], '\0'}; const char *tst; op = bolt_journal_op_from_string (str, &err); g_assert_no_error (err); tst = bolt_journal_op_to_string (op); g_assert_cmpstr (tst, ==, str); } op = bolt_journal_op_from_string ("XXX", &error); g_assert_cmpint (op, ==, BOLT_JOURNAL_FAILED); g_assert_error (error, BOLT_ERROR, BOLT_ERROR_FAILED); g_clear_pointer (&error, g_error_free); op = bolt_journal_op_from_string ("", &error); g_assert_cmpint (op, ==, BOLT_JOURNAL_FAILED); g_assert_error (error, BOLT_ERROR, BOLT_ERROR_FAILED); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/journal/object", TestJournal, NULL, test_journal_setup, test_journal_object, test_journal_tear_down); g_test_add ("/journal/create", TestJournal, NULL, test_journal_setup, test_journal_create, test_journal_tear_down); g_test_add ("/journal/ops", TestJournal, NULL, test_journal_setup, test_journal_insert, test_journal_tear_down); g_test_add ("/journal/diff", TestJournal, NULL, test_journal_setup, test_journal_diff, test_journal_tear_down); g_test_add ("/journal/diff/fresh", TestJournal, NULL, test_journal_setup, test_journal_diff_fresh, test_journal_tear_down); g_test_add ("/journal/invalid_file", TestJournal, NULL, test_journal_setup, test_journal_invalid_file, test_journal_tear_down); g_test_add ("/journal/op/string", TestJournal, NULL, NULL, test_journal_op_stringops, NULL); return g_test_run (); } bolt-0.9.2/tests/test-logging.c000066400000000000000000000301731417453051000163770ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-dbus.h" #include "bolt-device.h" #include "bolt-domain.h" #include "bolt-error.h" #include "bolt-str.h" #include "bolt-term.h" #include "bolt-log.h" #include #include #include #include #include #include #include typedef struct _LogData { GLogLevelFlags level; GHashTable *fields; } LogData; typedef struct _TestLog { LogData data; } TestLog; static void test_log_setup (TestLog *tt, gconstpointer user_data) { tt->data.fields = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_log_set_writer_func (g_log_writer_standard_streams, NULL, NULL); } static void test_log_tear_down (TestLog *tt, gconstpointer user_data) { g_hash_table_destroy (tt->data.fields); } static void log_expectv (TestLog *tt, GLogLevelFlags level, const char *domain, const char *message, va_list args) { const char *key; tt->data.level = level; g_hash_table_remove_all (tt->data.fields); if (domain) g_hash_table_insert (tt->data.fields, g_strdup ("GLIB_DOMAIN"), g_strdup (domain)); if (message) g_hash_table_insert (tt->data.fields, g_strdup ("MESSAGE"), g_strdup (message)); while ((key = va_arg (args, const char *)) != NULL) { const char *val = va_arg (args, const char *); g_hash_table_insert (tt->data.fields, g_strdup (key), g_strdup (val)); } } static void log_expect (TestLog *tt, GLogLevelFlags level, const char *domain, const char *message, ...) { va_list args; va_start (args, message); log_expectv (tt, level, domain, message, args); va_end (args); } static GLogWriterOutput test_writer (GLogLevelFlags log_level, const GLogField *fields, gsize n_fields, gpointer user_data) { g_autoptr(GHashTable) index = NULL; LogData *data = user_data; GHashTableIter iter; gpointer k, v; index = g_hash_table_new (g_str_hash, g_str_equal); for (gsize i = 0; i < n_fields; i++) { const char *key = fields[i].key; const char *val = (const char *) fields[i].value; if (!g_hash_table_contains (data->fields, key)) continue; g_hash_table_insert (index, (gpointer) key, (gpointer) val); } g_hash_table_iter_init (&iter, data->fields); while (g_hash_table_iter_next (&iter, &k, &v)) { const char *key = k; const char *value = v; const char *have; g_assert_true (g_hash_table_contains (index, key)); have = g_hash_table_lookup (index, key); g_assert_cmpstr (value, ==, have); //g_fprintf (stderr, "%s: %s vs %s\n", key, value, have); } return G_LOG_WRITER_HANDLED; } static void test_log_basic (TestLog *tt, gconstpointer user_data) { log_expect (tt, G_LOG_LEVEL_MESSAGE, "bolt-test", "test", NULL); g_log_set_writer_func (test_writer, &tt->data, NULL); bolt_log ("bolt-test", G_LOG_LEVEL_MESSAGE, "test"); g_assert_nonnull (bolt_log_level_to_string (G_LOG_LEVEL_ERROR)); g_assert_nonnull (bolt_log_level_to_string (G_LOG_LEVEL_CRITICAL)); g_assert_nonnull (bolt_log_level_to_string (G_LOG_LEVEL_WARNING)); g_assert_nonnull (bolt_log_level_to_string (G_LOG_LEVEL_MESSAGE)); g_assert_nonnull (bolt_log_level_to_string (G_LOG_LEVEL_INFO)); g_assert_nonnull (bolt_log_level_to_string (G_LOG_LEVEL_DEBUG)); g_assert_nonnull (bolt_log_level_to_priority (G_LOG_LEVEL_ERROR)); g_assert_nonnull (bolt_log_level_to_priority (G_LOG_LEVEL_CRITICAL)); g_assert_nonnull (bolt_log_level_to_priority (G_LOG_LEVEL_WARNING)); g_assert_nonnull (bolt_log_level_to_priority (G_LOG_LEVEL_MESSAGE)); g_assert_nonnull (bolt_log_level_to_priority (G_LOG_LEVEL_INFO)); g_assert_nonnull (bolt_log_level_to_priority (G_LOG_LEVEL_DEBUG)); } static void test_log_gerror (TestLog *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; const char *domain = "bolt-gerror"; GLogLevelFlags lvl = G_LOG_LEVEL_INFO; const char *msg; msg = "no udev"; g_set_error_literal (&error, BOLT_ERROR, BOLT_ERROR_UDEV, msg); log_expect (tt, lvl, domain, NULL, "ERROR_MESSAGE", msg, NULL); g_log_set_writer_func (test_writer, &tt->data, NULL); bolt_log (domain, lvl, LOG_ERR (error), NULL); /* check we handle NULL GErrors without crashing */ log_expect (tt, lvl, domain, NULL, "ERROR_MESSAGE", "unknown cause", BOLT_LOG_BUG_MARK, "*", NULL); bolt_log (domain, lvl, LOG_ERR (NULL), NULL); } static void test_log_device (TestLog *tt, gconstpointer user_data) { g_autoptr(BoltDevice) a = NULL; const char *domain = "bolt-device"; const char *msg; GLogLevelFlags lvl; const char *uid_a = "fbc83890-e9bf-45e5-a777-b3728490989c"; a = g_object_new (BOLT_TYPE_DEVICE, "uid", uid_a, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, NULL); lvl = G_LOG_LEVEL_INFO; msg = "test device a"; log_expect (tt, lvl, domain, msg, BOLT_LOG_DEVICE_UID, uid_a, NULL); g_log_set_writer_func (test_writer, &tt->data, NULL); bolt_log (domain, lvl, LOG_DEV (a), msg); } static void test_log_macros (TestLog *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; GLogLevelFlags lvl = G_LOG_LEVEL_INFO; const char *msg = "da steht ich nun ich armer test"; g_log_set_writer_func (test_writer, &tt->data, NULL); log_expect (tt, G_LOG_LEVEL_MESSAGE, G_LOG_DOMAIN, msg, NULL); bolt_msg (msg); g_set_error_literal (&error, BOLT_ERROR, BOLT_ERROR_UDEV, msg); log_expect (tt, lvl, G_LOG_DOMAIN, NULL, "ERROR_MESSAGE", msg, NULL); bolt_warn_err (error, NULL); log_expect (tt, lvl, G_LOG_DOMAIN, NULL, BOLT_LOG_ERROR_MESSAGE, msg, NULL); bolt_log (G_LOG_DOMAIN, lvl, LOG_DIRECT (BOLT_LOG_ERROR_MESSAGE, msg), NULL); log_expect (tt, G_LOG_LEVEL_DEBUG, G_LOG_DOMAIN, msg, "CODE_FILE", __FILE__, "CODE_FUNC", G_STRFUNC, NULL); bolt_debug (msg); msg = "nasty bug"; log_expect (tt, G_LOG_LEVEL_DEBUG, G_LOG_DOMAIN, msg, BOLT_LOG_TOPIC, "code", BOLT_LOG_BUG_MARK, "*", NULL); bolt_bug (msg); } static GLogWriterOutput test_log_logger_stdstream (GLogLevelFlags level, const GLogField *fields, gsize n_fields, gpointer user_data) { g_autoptr(BoltLogCtx) ctx = NULL; g_return_val_if_fail (fields != NULL, G_LOG_WRITER_UNHANDLED); g_return_val_if_fail (n_fields > 0, G_LOG_WRITER_UNHANDLED); ctx = bolt_log_ctx_acquire (fields, n_fields); if (ctx == NULL) return G_LOG_WRITER_UNHANDLED; return bolt_log_stdstream (ctx, level, 0); } static GLogWriterOutput test_log_logger_journal (GLogLevelFlags level, const GLogField *fields, gsize n_fields, gpointer user_data) { g_autoptr(BoltLogCtx) ctx = NULL; char message[2048] = {0, }; const char *dom; g_return_val_if_fail (fields != NULL, G_LOG_WRITER_UNHANDLED); g_return_val_if_fail (n_fields > 0, G_LOG_WRITER_UNHANDLED); ctx = bolt_log_ctx_acquire (fields, n_fields); if (ctx == NULL) return G_LOG_WRITER_UNHANDLED; dom = blot_log_ctx_get_domain (ctx); if (dom != NULL) fprintf (stderr, "DOMAIN: %s", dom); bolt_log_fmt_journal (ctx, level, message, sizeof (message)); fprintf (stderr, "%s\n", message); return G_LOG_WRITER_HANDLED; } static void test_log_logger (TestLog *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; const char *msg = NULL; const char *uid1 = "884c6edd-7118-4b21-b186-b02d396ecca0"; const char *uid2 = "884c6ede-7118-4b21-b186-b02d396ecca0"; const char *uid3 = "884c6edf-7118-4b21-b186-b02d396ecca0"; if (g_test_subprocess ()) { g_autoptr(BoltDomain) dom = NULL; g_autoptr(BoltDevice) dev = NULL; if (GPOINTER_TO_INT (user_data) == 1) g_log_set_writer_func (test_log_logger_journal, tt, NULL); else g_log_set_writer_func (test_log_logger_stdstream, tt, NULL); dom = g_object_new (BOLT_TYPE_DOMAIN, "id", "domain0", "uid", uid1, "bootacl", NULL, NULL); dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid2, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, NULL); msg = "no udev"; g_set_error_literal (&err, BOLT_ERROR, BOLT_ERROR_UDEV, msg); bolt_warn_err (err, LOG_TOPIC ("the_topic"), "WARNUNG-1"); bolt_log ("ck01", G_LOG_LEVEL_INFO, LOG_DEV (dev), "MESSAGE-%d", 1); bolt_log ("ck01", G_LOG_LEVEL_INFO, LOG_DOM (dom), "MESSAGE-%d", 2); bolt_log ("ck01", G_LOG_LEVEL_INFO, LOG_DEV_UID (uid3), "MESSAGE-%d", 3); g_warning ("WARNUNG-2"); g_critical ("WARNUNG-3"); g_log ("ck02", G_LOG_LEVEL_INFO, "MESSAGE-%d", 4); exit (0); } g_test_trap_subprocess (NULL, 0, 0); g_test_trap_assert_passed (); g_test_trap_assert_stderr ("*WARNUNG-1*"); g_test_trap_assert_stderr ("*the_topic*"); g_test_trap_assert_stderr ("*WARNUNG-2*"); g_test_trap_assert_stderr ("*WARNUNG-3*"); g_test_trap_assert_stderr ("*MESSAGE-1*"); g_test_trap_assert_stderr ("*MESSAGE-2*"); g_test_trap_assert_stderr ("*MESSAGE-3*"); g_test_trap_assert_stderr ("*MESSAGE-3*"); g_test_trap_assert_stderr ("*domain0*"); g_test_trap_assert_stderr ("*884c6edd*"); g_test_trap_assert_stderr ("*884c6ede*"); g_test_trap_assert_stderr ("*884c6edf*"); g_test_trap_assert_stderr ("*Laptop*"); if (GPOINTER_TO_INT (user_data) == 1) { g_test_trap_assert_stderr ("*DOMAIN: ck01*"); g_test_trap_assert_stderr ("*DOMAIN: ck02*"); } } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/logging/basic", TestLog, NULL, test_log_setup, test_log_basic, test_log_tear_down); g_test_add ("/logging/gerror", TestLog, NULL, test_log_setup, test_log_gerror, test_log_tear_down); g_test_add ("/logging/device", TestLog, NULL, test_log_setup, test_log_device, test_log_tear_down); g_test_add ("/logging/macros", TestLog, NULL, test_log_setup, test_log_macros, test_log_tear_down); g_test_add ("/logging/logger/stdstream", TestLog, GINT_TO_POINTER (0), test_log_setup, test_log_logger, test_log_tear_down); g_test_add ("/logging/logger/journal", TestLog, GINT_TO_POINTER (1), test_log_setup, test_log_logger, test_log_tear_down); return g_test_run (); } bolt-0.9.2/tests/test-power.c000066400000000000000000000453211417453051000161060ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-power.h" #include "bolt-dbus.h" #include "bolt-fs.h" #include "bolt-macros.h" #include "bolt-str.h" #include "mock-sysfs.h" #include #include #include #include #include #include #include #include #include #include typedef struct udev_device udev_device; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); typedef struct { MockSysfs *sysfs; BoltUdev *udev; char *rundir; } TestPower; static void test_power_setup (TestPower *tt, gconstpointer data) { g_autoptr(GError) err = NULL; tt->sysfs = mock_sysfs_new (); tt->udev = bolt_udev_new ("udev", NULL, &err); g_assert_no_error (err); g_assert_nonnull (tt->udev); tt->rundir = g_strdup (g_getenv ("BOLT_RUNDIR")); if (tt->rundir == NULL) tt->rundir = g_dir_make_tmp ("bolt.power.XXXXXX", &err); g_assert_no_error (err); g_assert_nonnull (tt->rundir); g_debug ("rundir at '%s'", tt->rundir); } static void test_power_tear_down (TestPower *tt, gconstpointer user) { g_autoptr(GError) err = NULL; gboolean ok; ok = bolt_fs_cleanup_dir (tt->rundir, &err); g_assert_no_error (err); g_assert_true (ok); g_clear_object (&tt->sysfs); g_clear_object (&tt->udev); g_clear_pointer (&tt->rundir, g_free); } static BoltPower * make_bolt_power_timeout (TestPower *tt, guint timeout) { g_autoptr(GError) err = NULL; BoltPower *power; power = g_initable_new (BOLT_TYPE_POWER, NULL, &err, "udev", tt->udev, "timeout", timeout, "rundir", tt->rundir, NULL); g_assert_no_error (err); g_assert_nonnull (power); return power; } static void test_power_basic (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; g_autoptr(GError) err = NULL; g_autoptr(BoltUdev) udev = NULL; g_autoptr(GFile) statedir = NULL; g_autoptr(BoltGuard) guard = NULL; g_autofree char *guard_id = NULL; g_autofree char *guard_who = NULL; g_autofree char *guard_path = NULL; g_autofree char *guard_fifo = NULL; g_autofree char *rundir = NULL; gulong guard_pid; BoltPowerState state; gboolean supported; gboolean on; const char *fp; guint timeout; power = make_bolt_power_timeout (tt, 0); g_object_get (power, "rundir", &rundir, "statedir", &statedir, "udev", &udev, "supported", &supported, "state", &state, "timeout", &timeout, NULL); g_assert_cmpstr (rundir, ==, tt->rundir); g_assert_nonnull (statedir); g_assert_nonnull (udev); g_assert (udev == tt->udev); g_assert_false (supported); g_assert (state == BOLT_FORCE_POWER_UNSET); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_UNSET); g_assert_cmpuint (timeout, ==, 0); /* force power is unsupported, check the error handling */ guard = bolt_power_acquire (power, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_null (guard); g_clear_pointer (&err, g_error_free); /* add the force power sysfs device, * now it must be reported as supported */ g_object_unref (power); fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); power = make_bolt_power_timeout (tt, 0); g_object_get (power, "supported", &supported, "state", &state, NULL); g_assert_true (supported); g_assert (state == BOLT_FORCE_POWER_UNSET); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_UNSET); /* set of ON */ guard = bolt_power_acquire (power, &err); g_assert_no_error (err); g_assert_nonnull (guard); g_object_get (power, "state", &state, NULL); g_assert (state == BOLT_FORCE_POWER_ON); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_true (on); g_object_get (guard, "id", &guard_id, "who", &guard_who, "path", &guard_path, "pid", &guard_pid, "fifo", &guard_fifo, NULL); g_assert_nonnull (guard_id); g_assert_nonnull (guard_who); g_assert_nonnull (guard_path); g_assert_cmpstr (guard_id, ==, "1"); g_assert_cmpstr (guard_who, ==, "boltd"); g_assert_cmpuint (guard_pid, ==, getpid ()); g_assert_cmpstr (guard_id, ==, bolt_guard_get_id (guard)); g_assert_cmpstr (guard_who, ==, bolt_guard_get_who (guard)); g_assert_cmpuint (guard_pid, ==, bolt_guard_get_pid (guard)); /* set of OFF */ g_clear_object (&guard); g_object_get (power, "state", &state, NULL); g_assert (state == BOLT_FORCE_POWER_OFF); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_OFF); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); } static void test_power_multiple (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; g_autoptr(GError) err = NULL; g_autoptr(BoltGuard) guard = NULL; GPtrArray *guards = NULL; BoltPowerState state; gboolean supported; gboolean on; const char *fp; fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); power = make_bolt_power_timeout (tt, 0); g_object_get (power, "supported", &supported, "state", &state, NULL); g_assert_true (supported); g_assert (state == BOLT_FORCE_POWER_UNSET); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); /* set to ON via first guard */ guard = bolt_power_acquire (power, &err); g_assert_no_error (err); g_assert_nonnull (guard); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_true (on); /* add one and remove it, nothing should change */ for (guint i = 0; i < 5; i++) { g_autoptr(BoltGuard) g = NULL; state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_true (on); g = bolt_power_acquire (power, &err); g_assert_no_error (err); g_assert_nonnull (g); /* nothing should change */ state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_true (on); } state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_true (on); /* set of OFF */ g_clear_object (&guard); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_OFF); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); /* now all at once */ guards = g_ptr_array_new_with_free_func (g_object_unref); for (guint i = 0; i < 5; i++) { BoltGuard *g = bolt_power_acquire (power, &err); g_ptr_array_add (guards, g); } state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_true (on); /* release all of the guards at once */ g_clear_pointer (&guards, g_ptr_array_unref); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_OFF); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); } static void on_notify_quit_loop (GObject *gobject, GParamSpec *pspec, gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); } static gboolean on_timeout_warn_quit_loop (gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); g_warning ("timeout reached"); return G_SOURCE_CONTINUE; } static void test_power_timeout (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; g_autoptr(GError) err = NULL; g_autoptr(BoltGuard) guard = NULL; g_autoptr(GMainLoop) loop = NULL; BoltPowerState state; gboolean supported; gboolean on; const char *fp; guint timeout; guint tid; fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); /* non-zero timeout */ power = make_bolt_power_timeout (tt, 10); g_object_get (power, "supported", &supported, "state", &state, "timeout", &timeout, NULL); g_assert_true (supported); g_assert (state == BOLT_FORCE_POWER_UNSET); g_assert_cmpuint (timeout, ==, 10); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); /* set to ON ... */ guard = bolt_power_acquire (power, &err); g_assert_no_error (err); g_assert_nonnull (guard); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); /* .. and OFF*/ g_clear_object (&guard); /* but with a timeout, so we should be on still */ state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_WAIT); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_true (on); loop = g_main_loop_new (NULL, FALSE); tid = g_timeout_add_seconds (5, on_timeout_warn_quit_loop, loop); g_signal_connect (power, "notify::state", G_CALLBACK (on_notify_quit_loop), loop); /* now we wait for a state change */ g_main_loop_run (loop); g_source_remove (tid); /* we should have one now */ state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_OFF); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); } static void test_power_recover_state (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; g_autoptr(BoltGuard) guard = NULL; g_autoptr(GError) err = NULL; BoltPowerState state; GFile *guarddir; const char *fp; fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); if (g_test_subprocess ()) { /* we are the subprocess, create a BoltPower instance * but simulate a non-clean shutdown */ power = make_bolt_power_timeout (tt, 20 * 1000); g_assert_no_error (err); g_assert_nonnull (power); guard = bolt_power_acquire (power, &err); g_assert_no_error (err); g_assert_nonnull (guard); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_ON); g_clear_object (&guard); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_WAIT); g_debug ("simulating crashing boltd"); exit (EXIT_SUCCESS); } // the main test g_setenv ("BOLT_RUNDIR", tt->rundir, TRUE); g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR); g_test_trap_assert_passed (); power = make_bolt_power_timeout (tt, 10); g_assert_no_error (err); g_assert_nonnull (power); guarddir = bolt_power_get_statedir (power); g_assert_nonnull (guarddir); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_WAIT); g_unsetenv ("BOLT_RUNDIR"); } static void test_power_recover_guards_fail (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; g_autoptr(BoltGuard) guard = NULL; g_autoptr(GError) err = NULL; BoltPowerState state; const char *fp; pid_t pid; int r; #if HAVE_ASAN g_test_skip ("Test does not work with ASAN yet."); return; #endif fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); pid = fork (); g_assert_cmpint (pid, !=, -1); if (pid == 0) { /* child */ power = make_bolt_power_timeout (tt, 10); g_assert_no_error (err); g_assert_nonnull (power); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); /* we pass in zero as pid, which means it will be our pid */ guard = bolt_power_acquire_full (power, "test", 0, &err); g_assert_no_error (err); g_assert_nonnull (guard); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_ON); exit (0); } /* parent */ pid = waitpid (pid, &r, 0); g_assert_cmpint (pid, >, 0); g_assert_cmpint (r, ==, 0); /* now lets recover the guard */ power = make_bolt_power_timeout (tt, 10); g_assert_no_error (err); g_assert_nonnull (power); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_WAIT); } static gboolean on_cb_close_fd (gpointer user_data) { int *fd = user_data; int r; g_debug ("closing fd"); r = close (*fd); g_assert_cmpint (r, >, -1); *fd = -1; return FALSE; } static void test_power_guards_fifo (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; g_autoptr(GError) err = NULL; g_autoptr(BoltGuard) guard = NULL; g_autoptr(GMainLoop) loop = NULL; BoltPowerState state; gboolean on; const char *fp; guint tid; int fd; fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); /* non-zero timeout */ power = make_bolt_power_timeout (tt, 0); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); /* set to ON ... */ guard = bolt_power_acquire (power, &err); g_assert_no_error (err); g_assert_nonnull (guard); state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); fd = bolt_guard_monitor (guard, &err); g_assert_no_error (err); g_assert_cmpint (fd, >, -1); /* we should still be ON and the guard still active, * because the event watcher still owns a reference */ g_clear_object (&guard); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_ON); loop = g_main_loop_new (NULL, FALSE); /* fail if we don't have anything after n seconds */ tid = g_timeout_add_seconds (5, on_timeout_warn_quit_loop, loop); /* schedule a closing of the fifo */ g_idle_add (on_cb_close_fd, (gpointer) & fd); g_signal_connect (power, "notify::state", G_CALLBACK (on_notify_quit_loop), loop); /* now we wait for the fifo to be closed */ g_main_loop_run (loop); g_source_remove (tid); /* we should have one now */ state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_OFF); on = mock_sysfs_force_power_enabled (tt->sysfs); g_assert_false (on); } static void test_power_wmi_uevent (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; g_autoptr(GMainLoop) loop = NULL; BoltPowerState state; gboolean supported; const char *fp; guint tid; /* now we add the wmi module */ fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); loop = g_main_loop_new (NULL, FALSE); power = make_bolt_power_timeout (tt, 0); g_object_get (power, "supported", &supported, "state", &state, NULL); g_assert_true (supported); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); /* UNLOAD */ g_debug ("UNLOAD"); mock_sysfs_force_power_unload (tt->sysfs); tid = g_timeout_add_seconds (5, on_timeout_warn_quit_loop, loop); g_signal_connect (power, "notify::supported", G_CALLBACK (on_notify_quit_loop), loop); /* we wait for a change in the state*/ g_main_loop_run (loop); g_source_remove (tid); g_object_get (power, "supported", &supported, "state", &state, NULL); g_assert_false (supported); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); /* LOAD */ g_debug ("LOAD"); mock_sysfs_force_power_load (tt->sysfs); tid = g_timeout_add_seconds (5, on_timeout_warn_quit_loop, loop); g_signal_connect (power, "notify::state", G_CALLBACK (on_notify_quit_loop), loop); /* we wait for a change in the state*/ g_main_loop_run (loop); g_source_remove (tid); g_object_get (power, "supported", &supported, "state", &state, NULL); g_assert_true (supported); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); state = bolt_power_get_state (power); g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/power/basic", TestPower, NULL, test_power_setup, test_power_basic, test_power_tear_down); g_test_add ("/power/mutli-guards", TestPower, NULL, test_power_setup, test_power_multiple, test_power_tear_down); g_test_add ("/power/timeout", TestPower, NULL, test_power_setup, test_power_timeout, test_power_tear_down); g_test_add ("/power/recover", TestPower, NULL, test_power_setup, test_power_recover_state, test_power_tear_down); g_test_add ("/power/guards/recover/fail", TestPower, NULL, test_power_setup, test_power_recover_guards_fail, test_power_tear_down); g_test_add ("/power/guards/fifo", TestPower, NULL, test_power_setup, test_power_guards_fifo, test_power_tear_down); g_test_add ("/power/wmi-uevent", TestPower, NULL, test_power_setup, test_power_wmi_uevent, test_power_tear_down); return g_test_run (); } bolt-0.9.2/tests/test-reaper.c000066400000000000000000000070161417453051000162270ustar00rootroot00000000000000/* * Copyright © 2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-reaper.h" #include "bolt-dbus.h" #include "bolt-unix.h" #include #include #include #include typedef struct { int dummy; } TestReaper; static gboolean warn_quit_loop (gpointer user_data) { GMainLoop *loop = user_data; g_warning ("timeout reached"); g_main_loop_quit (loop); return G_SOURCE_REMOVE; } static void process_died (GObject *gobject, guint pid, const char *name, gpointer user_data) { GMainLoop *loop = user_data; g_debug ("%u (%s) died", pid, name); if (g_main_loop_is_running (loop)) g_main_loop_quit (loop); } /* */ static void test_reaper_object (TestReaper *tt, gconstpointer user) { g_autoptr(BoltReaper) reaper = NULL; guint timeout; gboolean found; reaper = g_object_new (BOLT_TYPE_REAPER, NULL); g_object_get (reaper, "timeout", &timeout, NULL); g_assert_cmpuint (timeout, >, 0); g_clear_object (&reaper); reaper = g_object_new (BOLT_TYPE_REAPER, "timeout", 10, NULL); g_object_get (reaper, "timeout", &timeout, NULL); g_assert_cmpuint (timeout, ==, 10); bolt_reaper_add_pid (reaper, 23, NULL); found = bolt_reaper_has_pid (reaper, 23); g_assert_true (found); found = bolt_reaper_del_pid (reaper, 23); g_assert_true (found); found = bolt_reaper_del_pid (reaper, 23); g_assert_false (found); } static void test_reaper_basic (TestReaper *tt, gconstpointer user) { g_autoptr(BoltReaper) reaper = NULL; g_autoptr(GMainLoop) loop = NULL; guint tid; pid_t pid; int r; pid = fork (); g_assert_cmpint (pid, !=, -1); if (pid == 0) /* child, do nothing but exit */ exit (0); g_assert_true (bolt_pid_is_alive (pid)); pid = waitpid (pid, &r, 0); g_assert_cmpint (pid, >, 0); g_assert_cmpint (r, ==, 0); loop = g_main_loop_new (NULL, FALSE); reaper = g_object_new (BOLT_TYPE_REAPER, "timeout", 500, NULL); g_assert_nonnull (reaper); bolt_reaper_add_pid (reaper, (pid_t) pid, "foo"); g_signal_connect (reaper, "process-died", G_CALLBACK (process_died), loop); tid = g_timeout_add_seconds (5, warn_quit_loop, loop); g_main_loop_run (loop); g_clear_handle_id (&tid, g_source_remove); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/reaper/object", TestReaper, NULL, NULL, test_reaper_object, NULL); g_test_add ("/reaper/basic", TestReaper, NULL, NULL, test_reaper_basic, NULL); return g_test_run (); } bolt-0.9.2/tests/test-self.c000066400000000000000000000143251417453051000157030ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include #include typedef int TestDummy; static void test_version_parse (TestDummy *tt, gconstpointer user_data) { struct VersionTest { const char *str; /* result */ gboolean ok; /* data */ int major; int minor; int patch; const char *suffix; } ftt[] = { {"", FALSE, -1, -1, -1, NULL}, {"parsererror", FALSE, -1, -1, -1, NULL}, {"parser.err.or", FALSE, -1, -1, -1, NULL}, {"1.0.0.43", FALSE, 1, 0, -1, NULL}, {"1", TRUE, 1, -1, -1, NULL}, {"1.0", TRUE, 1, 0, -1, NULL}, {"1.0.0", TRUE, 1, 0, 0, NULL}, {"1-100", TRUE, 1, -1, -1, "100"}, {"1-100.fc", TRUE, 1, -1, -1, "100.fc"}, {"1.0-100.fc", TRUE, 1, 0, -1, "100.fc"}, {"1.0.0-100.fc", TRUE, 1, 0, 0, "100.fc"}, {"5.2.11-200.fc30.x86_64", TRUE, 5, 2, 11, "200.fc30.x86_64"}, {"4.4.0-161-generic", TRUE, 4, 4, 0, "161-generic"} }; for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) { g_autoptr(GError) err = NULL; g_auto(BoltVersion) v = { .major = 42, .minor = 42, .patch = 42, .suffix = NULL}; gboolean ok; ok = bolt_version_parse (ftt[i].str, &v, &err); if (ftt[i].ok) { g_assert_no_error (err); g_assert_true (ok); } else { g_assert_nonnull (err); g_assert_false (ok); } g_assert_cmpint (v.major, ==, ftt[i].major); g_assert_cmpint (v.minor, ==, ftt[i].minor); g_assert_cmpint (v.patch, ==, ftt[i].patch); if (ftt[i].suffix) g_assert_cmpstr (v.suffix, ==, ftt[i].suffix); else g_assert_null (v.suffix); } } static void test_version_compare (TestDummy *tt, gconstpointer user_data) { struct VersionTest { BoltVersion a; BoltVersion b; int res; } ftt[] = { /* x.-.- */ { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT ( 0, -1, -1), 1}, { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT ( 1, -1, -1), 0}, { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT (42, -1, -1), -1}, { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 1, -1, -1), 1}, { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 5, 1, -1), -1}, /* x.y.- */ { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 0, 5, -1), 1}, { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 1, 0, -1), 1}, { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 2, 0, -1), -1}, /* x.y.z */ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 0, 0), 1}, { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 2), 1}, { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 3), 0}, { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 4), -1}, { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, 0, 0), -1}, { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, 0, -1), -1}, { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, -1, -1), -1}, }; for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) { int res = bolt_version_compare (&(ftt[i].a), &(ftt[i].b)); g_assert_cmpint (res, ==, ftt[i].res); res = bolt_version_compare (&(ftt[i].b), &(ftt[i].a)); g_assert_cmpint (res, ==, -1 * ftt[i].res); } } static void test_version_check (TestDummy *tt, gconstpointer user_data) { struct VersionTest { BoltVersion version; int major; int minor; int patch; gboolean res; } ftt[] = { { BOLT_VERSION_INIT (1, 2, 3), 1, -1, -1, TRUE}, { BOLT_VERSION_INIT (1, 2, 3), 1, 0, -1, TRUE}, { BOLT_VERSION_INIT (1, 2, 3), 1, 0, 0, TRUE}, { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 0, TRUE}, { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 3, TRUE}, { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 4, FALSE}, { BOLT_VERSION_INIT (1, 2, 3), 1, 3, 2, FALSE}, { BOLT_VERSION_INIT (1, 2, 3), 2, 0, 0, FALSE}, { BOLT_VERSION_INIT (1, 2, 3), 2, 3, 0, FALSE}, { BOLT_VERSION_INIT (1, 2, 3), 2, 3, 4, FALSE}, { BOLT_VERSION_INIT (1, 2, 3), 2, -1, -1, FALSE}, { BOLT_VERSION_INIT (1, 2, 3), 2, 0, -1, FALSE}, { BOLT_VERSION_INIT (2, -1, -1), 2, -1, -1, TRUE}, { BOLT_VERSION_INIT (2, -1, -1), 1, 9, 9, TRUE}, { BOLT_VERSION_INIT (2, -1, -1), 2, 0, 0, FALSE}, }; for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) { gboolean res; res = bolt_version_check (&(ftt[i].version), ftt[i].major, ftt[i].minor, ftt[i].patch); g_assert_true (res == ftt[i].res); } } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_test_add ("/self/version/parse", TestDummy, NULL, NULL, test_version_parse, NULL); g_test_add ("/self/version/compare", TestDummy, NULL, NULL, test_version_compare, NULL); g_test_add ("/self/version/check", TestDummy, NULL, NULL, test_version_check, NULL); return g_test_run (); } bolt-0.9.2/tests/test-store.c000066400000000000000000000655761417453051000161240ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-dbus.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-io.h" #include "bolt-str.h" #include "bolt-test.h" #include "bolt-config.h" #include "bolt-store.h" #include "mock-sysfs.h" #include #include #include #include #include #include #include #include #include /* unlinkat, truncate */ typedef struct { char *path; BoltStore *store; } TestStore; static void test_store_setup (TestStore *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; tt->path = g_dir_make_tmp ("bolt.auth.XXXXXX", &error); if (tt->path == NULL) { g_critical ("Could not create tmp dir: %s", error->message); return; } tt->store = bolt_store_new (tt->path, &error); if (tt->store == NULL) { g_critical ("Could not create store at %s: %s", tt->path, error->message); return; } g_debug ("store at '%s'", tt->path); } static void test_store_tear_down (TestStore *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; gboolean ok; g_clear_object (&tt->store); ok = bolt_fs_cleanup_dir (tt->path, &error); if (!ok) g_warning ("Could not clean up dir: %s", error->message); g_free (tt->path); } static void test_store_basic (TestStore *tt, gconstpointer user_data) { g_autoptr(BoltDevice) dev = NULL; g_autoptr(BoltDevice) stored = NULL; g_autoptr(BoltKey) key = NULL; g_autoptr(GFile) root = NULL; g_autoptr(GError) error = NULL; g_autofree char *path = NULL; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; BoltKeyState keystate; guint version; gboolean ok; g_object_get (tt->store, "root", &root, NULL); path = g_file_get_path (root); g_assert_cmpstr (tt->path, ==, path); g_object_get (tt->store, "version", &version, NULL); g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); version = bolt_store_get_version (tt->store); g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, NULL); stored = bolt_store_get_device (tt->store, uid, &error); g_assert_nonnull (error); g_assert_true (bolt_err_notfound (error)); g_assert_null (stored); g_clear_error (&error); ok = bolt_store_put_device (tt->store, dev, BOLT_POLICY_AUTO, NULL, &error); g_assert_no_error (error); g_assert_true (ok); stored = bolt_store_get_device (tt->store, uid, &error); g_assert_no_error (error); g_assert_nonnull (stored); keystate = bolt_store_have_key (tt->store, uid); g_assert_cmpuint (keystate, ==, 0); g_assert_cmpstr (bolt_device_get_uid (stored), ==, bolt_device_get_uid (dev)); g_assert_cmpstr (bolt_device_get_name (stored), ==, bolt_device_get_name (dev)); g_assert_cmpstr (bolt_device_get_vendor (stored), ==, bolt_device_get_vendor (dev)); g_assert_cmpuint (bolt_device_get_generation (stored), ==, 0); g_assert_cmpuint (bolt_device_get_policy (stored), ==, BOLT_POLICY_AUTO); g_assert_cmpuint (bolt_device_get_stored (stored), ==, TRUE); g_assert_cmpuint (bolt_device_get_keystate (stored), ==, BOLT_KEY_MISSING); g_clear_object (&stored); g_clear_object (&dev); uid[0] = 'a'; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "generation", 4, "status", BOLT_STATUS_DISCONNECTED, NULL); stored = bolt_store_get_device (tt->store, uid, &error); g_assert_nonnull (error); g_assert_true (bolt_err_notfound (error)); g_assert_null (stored); g_clear_error (&error); g_assert_no_error (error); key = bolt_key_new (NULL); g_assert_nonnull (key); ok = bolt_store_put_device (tt->store, dev, BOLT_POLICY_MANUAL, key, &error); g_assert_no_error (error); g_assert_true (ok); stored = bolt_store_get_device (tt->store, uid, &error); g_assert_no_error (error); g_assert_nonnull (stored); g_assert_cmpstr (bolt_device_get_uid (stored), ==, bolt_device_get_uid (dev)); g_assert_cmpstr (bolt_device_get_name (stored), ==, bolt_device_get_name (dev)); g_assert_cmpstr (bolt_device_get_vendor (stored), ==, bolt_device_get_vendor (dev)); g_assert_cmpuint (bolt_device_get_generation (stored), ==, 4); g_assert_cmpuint (bolt_device_get_policy (stored), ==, BOLT_POLICY_MANUAL); g_assert_cmpuint (bolt_device_get_stored (stored), ==, TRUE); g_assert_cmpuint (bolt_device_get_keystate (stored), ==, 1); keystate = bolt_store_have_key (tt->store, uid); g_assert_cmpuint (keystate, ==, 1); g_clear_object (&key); key = bolt_store_get_key (tt->store, uid, &error); g_assert_no_error (error); g_assert_nonnull (stored); /* ** deletion */ /* non-existent */ ok = bolt_store_del_device (tt->store, "transmogrifier", &error); g_assert_nonnull (error); g_assert_true (bolt_err_notfound (error)); g_assert_false (ok); g_clear_error (&error); g_assert_no_error (error); ok = bolt_store_del_key (tt->store, "sesamoeffnedich", &error); g_assert_nonnull (error); g_assert_true (bolt_err_notfound (error)); g_assert_false (ok); g_clear_error (&error); g_assert_no_error (error); /* remove existing device & key */ ok = bolt_store_del_device (tt->store, uid, &error); g_assert_no_error (error); g_assert_true (ok); keystate = bolt_store_have_key (tt->store, uid); g_assert_cmpuint (keystate, !=, 0); ok = bolt_store_del_key (tt->store, uid, &error); g_assert_no_error (error); g_assert_true (ok); /* check that they are gone indeed */ ok = bolt_store_del_device (tt->store, uid, &error); g_assert_nonnull (error); g_assert_true (bolt_err_notfound (error)); g_assert_false (ok); g_clear_error (&error); g_assert_no_error (error); keystate = bolt_store_have_key (tt->store, uid); g_assert_cmpuint (keystate, ==, 0); ok = bolt_store_del_key (tt->store, uid, &error); g_assert_nonnull (error); g_assert_true (bolt_err_notfound (error)); g_assert_false (ok); g_clear_error (&error); g_assert_no_error (error); } static void test_store_update (TestStore *tt, gconstpointer user_data) { g_autoptr(BoltDevice) dev = NULL; g_autoptr(BoltKey) key = NULL; g_autoptr(GError) err = NULL; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; BoltKeyState keystate; BoltPolicy policy; gboolean ok; guint64 storetime; policy = BOLT_POLICY_IOMMU; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, "generation", 1, NULL); key = bolt_key_new (NULL); g_assert_nonnull (key); ok = bolt_store_put_device (tt->store, dev, policy, key, &err); g_assert_no_error (err); g_assert_true (ok); keystate = bolt_device_get_keystate (dev); g_assert_cmpuint (keystate, ==, BOLT_KEY_NEW); g_assert_cmpuint (bolt_device_get_policy (dev), ==, policy); storetime = bolt_device_get_storetime (dev); g_object_set (G_OBJECT (dev), "generation", 3, "label", "My Laptop", NULL); /* update the device. generation and label should * change, but the rest should stay the same, esp. * keystate and also storetime should not change. * Also, BOLT_POLICY_DEFAULT should be ignored */ ok = bolt_store_put_device (tt->store, dev, BOLT_POLICY_DEFAULT, NULL, &err); g_assert_no_error (err); g_assert_true (ok); keystate = bolt_device_get_keystate (dev); g_assert_cmpuint (keystate, ==, BOLT_KEY_NEW); g_assert_cmpuint (bolt_device_get_policy (dev), ==, policy); g_assert_cmpuint (bolt_device_get_storetime (dev), ==, storetime); } static void test_store_config (TestStore *tt, gconstpointer user_data) { g_autoptr(GKeyFile) kf = NULL; g_autoptr(GKeyFile) loaded = NULL; g_autoptr(GError) err = NULL; BoltAuthMode authmode; BoltPolicy policy; gboolean ok; BoltTri tri; kf = bolt_store_config_load (tt->store, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (kf); g_clear_pointer (&err, g_error_free); kf = bolt_config_user_init (); g_assert_nonnull (kf); ok = bolt_store_config_save (tt->store, kf, &err); g_assert_no_error (err); g_assert_true (ok); loaded = bolt_store_config_load (tt->store, &err); g_assert_no_error (err); g_assert_nonnull (loaded); tri = bolt_config_load_default_policy (loaded, &policy, &err); g_assert_no_error (err); g_assert (tri == TRI_NO); /* */ bolt_config_set_auth_mode (kf, "WRONG"); ok = bolt_store_config_save (tt->store, kf, &err); g_assert_no_error (err); g_assert_true (ok); g_clear_pointer (&loaded, g_key_file_unref); loaded = bolt_store_config_load (tt->store, &err); g_assert_no_error (err); g_assert_nonnull (loaded); tri = bolt_config_load_auth_mode (loaded, &authmode, &err); g_assert_error (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); g_assert (tri == TRI_ERROR); g_clear_pointer (&err, g_error_free); /* */ bolt_config_set_auth_mode (kf, "enabled"); ok = bolt_store_config_save (tt->store, kf, &err); g_assert_no_error (err); g_assert_true (ok); g_clear_pointer (&loaded, g_key_file_unref); loaded = bolt_store_config_load (tt->store, &err); g_assert_no_error (err); g_assert_nonnull (loaded); tri = bolt_config_load_auth_mode (loaded, &authmode, &err); g_assert_no_error (err); g_assert (tri == TRI_YES); g_assert_cmpuint (authmode, ==, BOLT_AUTH_ENABLED); } static void test_key (TestStore *tt, gconstpointer user_data) { g_autoptr(BoltKey) key = NULL; g_autoptr(BoltKey) loaded = NULL; g_autoptr(GFile) base = NULL; g_autoptr(GFile) f = NULL; g_autoptr(GError) err = NULL; g_autoptr(GFileInfo) fi = NULL; g_autofree char *p = NULL; gboolean fresh = FALSE; gboolean ok; guint32 mode; int r; key = bolt_key_new (NULL); g_assert_nonnull (key); g_object_get (key, "fresh", &fresh, NULL); g_assert_true (fresh); fresh = bolt_key_get_state (key); g_assert_true (fresh); base = g_file_new_for_path (tt->path); f = g_file_get_child (base, "key"); g_assert_nonnull (base); g_assert_nonnull (f); ok = bolt_key_save_file (key, f, &err); g_assert_no_error (err); g_assert_true (ok); fi = g_file_query_info (f, "*", 0, NULL, &err); g_assert_no_error (err); g_assert_nonnull (fi); mode = g_file_info_get_attribute_uint32 (fi, "unix::mode"); g_assert_cmpuint (mode & 0666, ==, 0600); loaded = bolt_key_load_file (f, &err); g_assert_no_error (err); g_assert_nonnull (loaded); g_clear_object (&loaded); /* corrupt the key */ p = g_file_get_path (f); r = truncate (p, 32); g_assert_cmpint (r, ==, 0); loaded = bolt_key_load_file (f, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_BADKEY); g_assert_null (loaded); g_clear_error (&err); /* empty key file ("", or "\n") */ for (gssize i = 0; i < 2; i++) { ok = g_file_set_contents (p, "\n", i, &err); g_assert_no_error (err); g_assert_true (ok); loaded = bolt_key_load_file (f, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_NOKEY); g_assert_null (loaded); g_clear_error (&err); } } static GLogWriterOutput null_logger (GLogLevelFlags log_level, const GLogField *fields, gsize n_fields, gpointer user_data) { return G_LOG_WRITER_HANDLED; } static void test_store_invalid_data (TestStore *tt, gconstpointer user_data) { g_autofree char *path = NULL; g_autofree char *fn = NULL; g_autoptr(GError) err = NULL; g_autoptr(BoltDevice) dev = NULL; static const char *uid = "399d33cb-c9cf-4273-8f92-9445437e0b43"; gboolean ok; int r; path = g_build_filename (tt->path, "devices", NULL); r = g_mkdir (path, 0755); g_assert_true (r == 0); fn = g_build_filename (path, uid, NULL); ok = g_file_set_contents (fn, "", 0, &err); g_assert_no_error (err); g_assert_true (ok); g_log_set_writer_func (null_logger, NULL, NULL); dev = bolt_store_get_device (tt->store, uid, &err); g_log_set_writer_func (g_log_writer_default, NULL, NULL); g_assert_null (dev); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_FAILED); } static void test_store_times (TestStore *tt, gconstpointer user_data) { g_autoptr(BoltDevice) dev = NULL; g_autoptr(BoltDevice) stored = NULL; g_autoptr(GError) error = NULL; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; guint64 authin = 574423871; guint64 connin = 574416000; guint64 authout; guint64 connout; gboolean ok; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, "authtime", authin, "conntime", connin, NULL); stored = bolt_store_get_device (tt->store, uid, &error); g_assert_nonnull (error); g_assert_true (bolt_err_notfound (error)); g_assert_null (stored); g_clear_error (&error); /* store the device with times */ ok = bolt_store_put_device (tt->store, dev, BOLT_POLICY_AUTO, NULL, &error); g_assert_no_error (error); g_assert_true (ok); /* verify the store has recorded the times */ ok = bolt_store_get_time (tt->store, uid, "authtime", &authout, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (authout, ==, authin); ok = bolt_store_get_time (tt->store, uid, "conntime", &connout, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (connout, ==, connin); /* check a newly loaded device has the times */ stored = bolt_store_get_device (tt->store, uid, &error); g_assert_no_error (error); g_assert_nonnull (stored); connout = bolt_device_get_conntime (stored); authout = bolt_device_get_authtime (stored); g_assert_cmpuint (connout, ==, connin); g_assert_cmpuint (authout, ==, authin); /* update the times */ connin = 8688720; authin = 9207120; ok = bolt_store_put_times (tt->store, uid, &error, "conntime", connin, "authtime", authin, NULL); g_assert_no_error (error); g_assert_true (ok); /* verify via store */ ok = bolt_store_get_time (tt->store, uid, "authtime", &authout, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (authout, ==, authin); ok = bolt_store_get_time (tt->store, uid, "conntime", &connout, &error); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (connout, ==, connin); authout = connout = 0; bolt_store_get_times (tt->store, uid, &error, "authtime", &authout, "conntime", &connout, NULL); g_assert_no_error (error); g_assert_true (ok); g_assert_cmpuint (connout, ==, connin); g_assert_cmpuint (authout, ==, authin); /* via the device loading */ g_clear_object (&stored); stored = bolt_store_get_device (tt->store, uid, &error); g_assert_no_error (error); g_assert_nonnull (stored); connout = bolt_device_get_conntime (stored); authout = bolt_device_get_authtime (stored); g_assert_cmpuint (connout, ==, connin); g_assert_cmpuint (authout, ==, authin); /* check property access */ connout = authout = 0; g_object_get (stored, "conntime", &connout, "authtime", &authout, NULL); g_assert_cmpuint (connout, ==, connin); g_assert_cmpuint (authout, ==, authin); /* lets remove them again */ ok = bolt_store_del_time (tt->store, uid, "conntime", &error); g_assert_no_error (error); g_assert_true (ok); connout = 0; ok = bolt_store_get_time (tt->store, uid, "conntime", &connout, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_error (&error); /* the multiple timestamp version of del is * ignoring not found errors */ ok = bolt_store_del_times (tt->store, uid, &error, "authtime", "conntime", NULL); g_assert_no_error (error); g_assert_true (ok); connout = 0; ok = bolt_store_get_time (tt->store, uid, "authtime", &connout, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_error (&error); /* check the time is not there, via the device loading */ g_clear_object (&stored); stored = bolt_store_get_device (tt->store, uid, &error); g_assert_no_error (error); g_assert_nonnull (stored); connout = bolt_device_get_conntime (stored); g_assert_cmpuint (connout, ==, 0); } static void test_store_domain (TestStore *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_auto(GStrv) uids = NULL; g_autoptr(BoltDomain) d1 = NULL; g_autoptr(BoltDomain) s1 = NULL; const char *uid = "884c6edd-7118-4b21-b186-b02d396ecca0"; gboolean exists; gboolean ok; GStrv bootacl = NULL; const char *acl[16] = { "884c6edd-7118-4b21-b186-b02d396ecca1", "884c6edd-7118-4b21-b186-b02d396ecca2", "", "884c6edd-7118-4b21-b186-b02d396ecca3", NULL, }; uids = bolt_store_list_uids (tt->store, "domains", &err); g_assert_no_error (err); g_assert_nonnull (uids); g_assert_cmpuint (g_strv_length (uids), ==, 0); d1 = g_object_new (BOLT_TYPE_DOMAIN, "uid", uid, "bootacl", NULL, NULL); g_assert_false (bolt_domain_is_stored (d1)); g_assert_false (bolt_domain_supports_bootacl (d1)); /* store */ ok = bolt_store_put_domain (tt->store, d1, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (bolt_domain_is_stored (d1)); g_assert_false (bolt_domain_supports_bootacl (d1)); g_clear_pointer (&uids, g_strfreev); /* list */ uids = bolt_store_list_uids (tt->store, "domains", &err); g_assert_no_error (err); g_assert_nonnull (uids); g_assert_cmpuint (g_strv_length (uids), ==, 1); g_assert_cmpstr (uids[0], ==, uid); /* get */ s1 = bolt_store_get_domain (tt->store, uid, &err); g_assert_no_error (err); g_assert_nonnull (s1); g_assert_true (bolt_domain_is_stored (s1)); g_assert_false (bolt_domain_supports_bootacl (s1)); g_assert_cmpstr (uid, ==, bolt_domain_get_uid (s1)); bootacl = bolt_domain_get_bootacl (s1); g_assert_null (bootacl); /* update the bootacl */ g_object_set (d1, "bootacl", acl, NULL); g_assert_true (bolt_domain_supports_bootacl (d1)); ok = bolt_store_put_domain (tt->store, d1, &err); g_assert_no_error (err); g_assert_true (ok); exists = bolt_store_has_journal (tt->store, "bootacl", uid); g_assert_true (exists); ok = bolt_domain_can_delete (d1, &err); g_assert_no_error (err); g_assert_true (ok); /* update: get again after update */ g_clear_object (&s1); s1 = bolt_store_get_domain (tt->store, uid, &err); g_assert_no_error (err); g_assert_nonnull (s1); g_assert_true (bolt_domain_is_stored (s1)); g_assert_true (bolt_domain_supports_bootacl (d1)); bootacl = bolt_domain_get_bootacl (s1); bolt_assert_strv_equal ((GStrv) acl, bootacl, 0); g_clear_object (&s1); /* delete */ g_assert_true (bolt_domain_is_stored (d1)); ok = bolt_store_del_domain (tt->store, d1, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (bolt_domain_is_stored (d1)); exists = bolt_store_has_journal (tt->store, "bootacl", uid); g_assert_false (exists); /* store again, modify the bootacl, i.e. write to the journal */ ok = bolt_store_put_domain (tt->store, d1, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (bolt_domain_is_stored (d1)); ok = bolt_domain_bootacl_del (d1, "884c6edd-7118-4b21-b186-b02d396ecca1", &err); g_assert_no_error (err); g_assert_true (ok); bootacl = bolt_domain_get_bootacl (d1); acl[0] = ""; bolt_assert_strv_equal ((GStrv) acl, bootacl, 0); /* journal should exist and non-empty */ exists = bolt_store_has_journal (tt->store, "bootacl", uid); g_assert_true (exists); /* non-empty journal should prevent deletion */ ok = bolt_domain_can_delete (d1, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY); g_assert_false (ok); g_clear_error (&err); ok = bolt_store_del_domain (tt->store, d1, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY); g_assert_false (ok); } static void test_store_journal (TestStore *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(BoltJournal) journal = NULL; gboolean exists; gboolean ok; /* delete an non-existing one */ ok = bolt_store_del_journal (tt->store, "acl", "log", &err); g_assert_no_error (err); g_assert_true (ok); /* create a non-existing one */ journal = bolt_store_open_journal (tt->store, "acl", "log", &err); g_assert_no_error (err); g_assert_nonnull (journal); exists = bolt_store_has_journal (tt->store, "acl", "log"); g_assert_true (exists); /* re-open the existing one */ g_clear_object (&journal); journal = bolt_store_open_journal (tt->store, "acl", "log", &err); g_assert_no_error (err); g_assert_nonnull (journal); /* delete the journal */ g_clear_object (&journal); ok = bolt_store_del_journal (tt->store, "acl", "log", &err); g_assert_no_error (err); g_assert_true (ok); exists = bolt_store_has_journal (tt->store, "acl", "log"); g_assert_false (exists); } static void test_store_upgrade (TestStore *tt, gconstpointer user_data) { g_autoptr(BoltDevice) dev = NULL; g_autoptr(GError) err = NULL; g_autoptr(DIR) root = NULL; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; guint version; gboolean up; gboolean ok; /* simulate a version 0 store, i.e. has some entries, * but no 'version' file */ root = bolt_opendir (tt->path, &err); g_assert_no_error (err); g_assert_nonnull (root); version = bolt_store_get_version (tt->store); g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", "status", BOLT_STATUS_DISCONNECTED, NULL); ok = bolt_store_put_device (tt->store, dev, BOLT_POLICY_AUTO, NULL, &err); g_assert_no_error (err); g_assert_true (ok); /* close the store, delete 'version' */ g_clear_object (&tt->store); ok = bolt_unlink_at (dirfd (root), "version", 0, &err); g_assert_no_error (err); g_assert_true (ok); /* re-create the store object */ tt->store = bolt_store_new (tt->path, &err); g_assert_no_error (err); g_assert_nonnull (tt->store); version = bolt_store_get_version (tt->store); g_assert_cmpuint (version, ==, 0); /* no upgrade the store */ ok = bolt_store_upgrade (tt->store, &up, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (up); /* assert the upgrade changed the version */ version = bolt_store_get_version (tt->store); g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); /* upgrade again, check it did not do anything */ ok = bolt_store_upgrade (tt->store, &up, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (up); version = bolt_store_get_version (tt->store); g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); /* ensure 'upgrade' argument is optional */ ok = bolt_store_upgrade (tt->store, NULL, &err); g_assert_no_error (err); g_assert_true (ok); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/daemon/key", TestStore, NULL, test_store_setup, test_key, test_store_tear_down); g_test_add ("/daemon/store/basic", TestStore, NULL, test_store_setup, test_store_basic, test_store_tear_down); g_test_add ("/daemon/store/update", TestStore, NULL, test_store_setup, test_store_update, test_store_tear_down); g_test_add ("/daemon/store/config", TestStore, NULL, test_store_setup, test_store_config, test_store_tear_down); g_test_add ("/daemon/store/invalid_data", TestStore, NULL, test_store_setup, test_store_invalid_data, test_store_tear_down); g_test_add ("/daemon/store/times", TestStore, NULL, test_store_setup, test_store_times, test_store_tear_down); g_test_add ("/daemon/store/domain", TestStore, NULL, test_store_setup, test_store_domain, test_store_tear_down); g_test_add ("/daemon/store/journal", TestStore, NULL, test_store_setup, test_store_journal, test_store_tear_down); g_test_add ("/daemon/store/upgrade", TestStore, NULL, test_store_setup, test_store_upgrade, test_store_tear_down); return g_test_run (); } bolt-0.9.2/tests/test-sysfs.c000066400000000000000000001327401417453051000161230ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-dbus.h" #include "bolt-error.h" #include "bolt-macros.h" #include "bolt-store.h" #include "bolt-str.h" #include "bolt-sysfs.h" #include "bolt-domain.h" #include "bolt-test.h" #include "mock-sysfs.h" #include #include #include #include #include #include #include typedef struct udev_device udev_device; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); typedef struct { MockSysfs *sysfs; struct udev *udev; } TestSysfs; static void test_sysfs_setup (TestSysfs *tt, gconstpointer data) { tt->sysfs = mock_sysfs_new (); tt->udev = udev_new (); } static void test_sysfs_tear_down (TestSysfs *tt, gconstpointer user) { g_clear_object (&tt->sysfs); g_clear_pointer (&tt->udev, udev_unref); } static void count_domains (gpointer data, gpointer user_data) { int *n = user_data; (*n)++; } static void test_sysfs_device_get_unique_id (TestSysfs *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; g_autoptr(GError) err = NULL; const char *domain; const char *host; const char *syspath; const char *dev; const char *uid; MockDevId id = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Laptop", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", }; domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); g_assert_nonnull (domain); host = mock_sysfs_host_add (tt->sysfs, domain, &id); g_assert_nonnull (host); syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); uid = bolt_sysfs_device_get_unique_id (udevice, &err); g_assert_no_error (err); g_assert_cmpstr (uid, ==, id.unique_id); /* error case */ memset (&id, 0, sizeof (MockDevId)); dev = mock_sysfs_device_add (tt->sysfs, host, &id, 0, NULL, -1, NULL); g_assert_nonnull (dev); g_clear_pointer (&udevice, udev_device_unref); syspath = mock_sysfs_device_get_syspath (tt->sysfs, dev); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); uid = bolt_sysfs_device_get_unique_id (udevice, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_UDEV); g_assert_null (uid); } static void check_ident (TestSysfs *tt, const char *host) { g_auto(BoltIdent) ident = BOLT_IDENT_INIT; MockDevId ref = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Laptop", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", }; MockDevId id = ref; struct { const char *name; const char **source; gint *fallback; const char **target; } fields[] = { {"name", &id.device_name, &id.device_id, &ident.name }, {"vendor", &id.vendor_name, &id.vendor_id, &ident.vendor} }; for (gsize i = 0; i < G_N_ELEMENTS (fields); i++) { g_autoptr(udev_device) udevice = NULL; g_autoptr(GError) err = NULL; const char *syspath; const char *dev; gboolean ok; /* reset fields */ id = ref; /* set the target to NULL */ *(fields[i].source) = NULL; dev = mock_sysfs_device_add (tt->sysfs, host, &id, 0, NULL, -1, NULL); g_assert_nonnull (dev); syspath = mock_sysfs_device_get_syspath (tt->sysfs, dev); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); ok = bolt_sysfs_device_ident (udevice, &ident, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_nonnull (*(fields[i].target)); mock_sysfs_device_remove (tt->sysfs, dev); g_clear_pointer (&udevice, udev_device_unref); /* remove the fallback, re-plug the device */ *(fields[i].fallback) = 0; dev = mock_sysfs_device_add (tt->sysfs, host, &id, 0, NULL, -1, NULL); g_assert_nonnull (dev); syspath = mock_sysfs_device_get_syspath (tt->sysfs, dev); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); bolt_ident_clear (&ident); g_assert_null (*(fields[i].target)); /* fallback is removed */ ok = bolt_sysfs_device_ident (udevice, &ident, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_UDEV); g_assert_false (ok); g_assert_null (ident.udev); bolt_ident_clear (&ident); mock_sysfs_device_remove (tt->sysfs, dev); } } static void test_sysfs_device_ident (TestSysfs *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; g_autoptr(GError) err = NULL; g_auto(BoltIdent) ident = BOLT_IDENT_INIT; const char *domain; const char *host; const char *syspath; gboolean ok; MockDevId id = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Laptop", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", }; domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); g_assert_nonnull (domain); host = mock_sysfs_host_add (tt->sysfs, domain, &id); g_assert_nonnull (host); syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); ok = bolt_sysfs_device_ident (udevice, &ident, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (ident.udev == udevice); g_assert_cmpstr (id.device_name, ==, ident.name); g_assert_cmpstr (id.vendor_name, ==, ident.vendor); g_clear_pointer (&udevice, udev_device_unref); /* clear function */ bolt_ident_clear (&ident); g_assert_null (ident.udev); g_assert_null (ident.name); g_assert_null (ident.vendor); /* table based individual field checks */ check_ident (tt, host); } static void test_sysfs_host_ident (TestSysfs *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; g_autoptr(GError) err = NULL; g_auto(BoltIdent) ident = BOLT_IDENT_INIT; const char *domain; const char *host; const char *syspath; gboolean ok; MockDevId id = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Laptop", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", }; MockDevId icl = { .vendor_id = 0, .vendor_name = NULL, .device_id = 0, .device_name = NULL, .unique_id = id.unique_id, }; struct { const char *sys_vendor; const char *product_version; const char *product_name; /* test */ const char *vendor; const char *name; } dmi_ids[] = { {"Dell Inc.", "", "XPS 13", "Dell Inc.", "XPS 13"}, {"LENOVO", "Thinkpad", "ABCD", "Lenovo", "Thinkpad"} }; domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); g_assert_nonnull (domain); host = mock_sysfs_host_add (tt->sysfs, domain, &id); g_assert_nonnull (host); syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); ok = bolt_sysfs_host_ident (udevice, &ident, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (ident.udev == udevice); g_assert_cmpstr (id.device_name, ==, ident.name); g_assert_cmpstr (id.vendor_name, ==, ident.vendor); g_clear_pointer (&udevice, udev_device_unref); bolt_ident_clear (&ident); /* empty host identification and no dmi info, should fail*/ mock_sysfs_host_remove (tt->sysfs, host); host = mock_sysfs_host_add (tt->sysfs, domain, &icl); g_assert_nonnull (host); syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); ok = bolt_sysfs_host_ident (udevice, &ident, &err); g_assert_error (err, BOLT_ERROR, BOLT_ERROR_UDEV); g_assert_false (ok); g_clear_error (&err); /* fill in and check various dmi information */ for (gsize i = 0; i < G_N_ELEMENTS (dmi_ids); i++) { const char *dmi; dmi = mock_sysfs_dmi_id_add (tt->sysfs, dmi_ids[i].sys_vendor, dmi_ids[i].product_name, dmi_ids[i].product_version); g_assert_nonnull (dmi); ok = bolt_sysfs_host_ident (udevice, &ident, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpstr (dmi_ids[i].name, ==, ident.name); g_assert_cmpstr (dmi_ids[i].vendor, ==, ident.vendor); bolt_ident_clear (&ident); mock_sysfs_dmi_id_remove (tt->sysfs); } } static void test_sysfs_domain_for_device (TestSysfs *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; udev_device *dh; udev_device *dd; const char *domain; const char *host; const char *dock; const char *syspath; MockDevId hostid = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Laptop", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", }; MockDevId dockid = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Thunderbolt Dock", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca1", }; domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); g_assert_nonnull (domain); host = mock_sysfs_host_add (tt->sysfs, domain, &hostid); g_assert_nonnull (host); dock = mock_sysfs_device_add (tt->sysfs, host, &dockid, 0, NULL, 0, NULL); g_assert_nonnull (dock); syspath = mock_sysfs_device_get_syspath (tt->sysfs, dock); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); /* for the dock */ dd = bolt_sysfs_domain_for_device (udevice, &dh); g_assert_nonnull (dd); g_assert_nonnull (dh); g_assert_cmpstr (udev_device_get_syspath (dd), ==, mock_sysfs_domain_get_syspath (tt->sysfs, domain)); g_assert_cmpstr (udev_device_get_syspath (dh), ==, mock_sysfs_device_get_syspath (tt->sysfs, host)); /* for the host itself */ dd = bolt_sysfs_domain_for_device (dh, &dh); g_assert_nonnull (dd); g_assert_nonnull (dh); g_assert_cmpstr (udev_device_get_syspath (dd), ==, mock_sysfs_domain_get_syspath (tt->sysfs, domain)); g_assert_cmpstr (udev_device_get_syspath (dh), ==, mock_sysfs_device_get_syspath (tt->sysfs, host)); mock_sysfs_domain_remove (tt->sysfs, domain); } static void test_sysfs_info_for_device (TestSysfs *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; g_autoptr(GError) err = NULL; const char *domain; const char *host; const char *dock; const char *syspath; BoltDevInfo info; gboolean ok; MockDevId hostid = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Laptop", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", }; MockDevId dockid = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Thunderbolt Dock", .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca1", }; BoltLinkSpeed ls = { .rx.speed = 10, .rx.lanes = 1, .tx.speed = 20, .tx.lanes = 2 }; domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); g_assert_nonnull (domain); host = mock_sysfs_host_add (tt->sysfs, domain, &hostid); g_assert_nonnull (host); dock = mock_sysfs_device_add (tt->sysfs, host, &dockid, 0, NULL, 0, &ls); syspath = mock_sysfs_device_get_syspath (tt->sysfs, dock); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); ok = bolt_sysfs_info_for_device (udevice, TRUE, &info, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (info.full); g_assert_cmpstr (info.parent, ==, hostid.unique_id); g_assert_cmpstr (info.syspath, ==, syspath); g_assert_cmpuint (info.linkspeed.rx.speed, ==, ls.rx.speed); g_assert_cmpuint (info.linkspeed.rx.lanes, ==, ls.rx.lanes); g_assert_cmpuint (info.linkspeed.tx.speed, ==, ls.tx.speed); g_assert_cmpuint (info.linkspeed.tx.lanes, ==, ls.tx.lanes); } static void test_sysfs_nhi_id_for_domain (TestSysfs *tt, gconstpointer user) { guint32 nhi[] = {0x15d2, }; for (gsize i = 0; i < G_N_ELEMENTS (nhi); i++) { g_autoptr(GError) err = NULL; g_autoptr(udev_device) udev = NULL; const char *domain; const char *syspath; gboolean ok; guint32 want = nhi[i]; guint32 have; domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, "nhi", want, NULL); g_assert_nonnull (domain); syspath = mock_sysfs_domain_get_syspath (tt->sysfs, domain); g_assert_nonnull (syspath); udev = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udev); ok = bolt_sysfs_nhi_id_for_domain (udev, &have, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_cmpuint (have, ==, want); } } static void test_nhi_uuid_is_stable (TestSysfs *tt, gconstpointer user) { g_autoptr(GError) err = NULL; gboolean stable; gboolean ok; /* Titan Ridge */ ok = bolt_nhi_uuid_is_stable (0x15e8, &stable, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (stable); /* Ice Lake integrated TBT */ ok = bolt_nhi_uuid_is_stable (0x8a0d, &stable, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (stable); /* missing id */ ok = bolt_nhi_uuid_is_stable (0x000d, &stable, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); } static void test_sysfs_read_iommu (TestSysfs *tt, gconstpointer user) { const char *domain; const char *syspath; gboolean ok; domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); g_assert_nonnull (domain); syspath = mock_sysfs_domain_get_syspath (tt->sysfs, domain); g_assert_nonnull (syspath); /* no sysfs attribute at all */ { g_autoptr(GError) err = NULL; g_autoptr(udev_device) udev = NULL; gboolean iommu; udev = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udev); iommu = TRUE; /* we expect FALSE */ ok = bolt_sysfs_read_iommu (udev, &iommu, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (iommu); } /* sysfs attribute is "0" */ { g_autoptr(GError) err = NULL; g_autoptr(udev_device) udev = NULL; gboolean iommu; ok = mock_syfs_domain_iommu_set (tt->sysfs, domain, "0", &err); g_assert_no_error (err); g_assert_true (ok); udev = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udev); iommu = TRUE; /* we expect FALSE */ ok = bolt_sysfs_read_iommu (udev, &iommu, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (iommu); } /* sysfs attribute is "1" */ { g_autoptr(GError) err = NULL; g_autoptr(udev_device) udev = NULL; gboolean iommu; ok = mock_syfs_domain_iommu_set (tt->sysfs, domain, "1", &err); g_assert_no_error (err); g_assert_true (ok); udev = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udev); iommu = FALSE; /* now we expect TRUE */ ok = bolt_sysfs_read_iommu (udev, &iommu, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (iommu); } /* sysfs attribute contains garbage */ { g_autoptr(GError) err = NULL; g_autoptr(udev_device) udev = NULL; gboolean iommu; ok = mock_syfs_domain_iommu_set (tt->sysfs, domain, "garbage", &err); g_assert_no_error (err); g_assert_true (ok); udev = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udev); iommu = TRUE; /* should be unchanged */ ok = bolt_sysfs_read_iommu (udev, &iommu, &err); g_assert_nonnull (err); g_assert_false (ok); g_assert_true (iommu); } } static void test_sysfs_domains (TestSysfs *tt, gconstpointer user) { g_autoptr(GError) err = NULL; const char *ids[5]; BoltSecurity sl[5] = {BOLT_SECURITY_NONE, BOLT_SECURITY_DPONLY, BOLT_SECURITY_USER, BOLT_SECURITY_SECURE, BOLT_SECURITY_USBONLY}; BoltDomain *all[5] = {NULL, }; BoltDomain *domains = NULL; BoltDomain *iter; int n; n = bolt_sysfs_count_hosts (tt->udev, &err); g_assert_no_error (err); g_assert_cmpint (n, ==, 0); for (gsize i = 0; i < G_N_ELEMENTS (sl); i++) { g_autoptr(udev_device) udevice = NULL; g_autoptr(BoltDomain) dom = NULL; /* the list will own reference */ const char *syspath; char uid[37] = {0, }; MockDevId hostid = { .vendor_id = 0x42, .vendor_name = "GNOME.org", .device_id = 0x42, .device_name = "Laptop", .unique_id = (const char *) &uid, }; g_snprintf (uid, sizeof (uid), "884c6edd-7118-4b21-b186-b02d396ecc%02x", (unsigned int) i); ids[i] = mock_sysfs_domain_add (tt->sysfs, sl[i], NULL); syspath = mock_sysfs_domain_get_syspath (tt->sysfs, ids[i]); udevice = udev_device_new_from_syspath (tt->udev, syspath); mock_sysfs_host_add (tt->sysfs, ids[i], &hostid); g_assert_nonnull (udevice); g_debug ("uid: %s", uid); dom = bolt_domain_new_for_udev (udevice, uid, &err); g_assert_no_error (err); g_assert_nonnull (dom); g_assert_cmpstr (syspath, ==, bolt_domain_get_syspath (dom)); g_assert_cmpstr (uid, ==, bolt_domain_get_uid (dom)); g_assert_cmpstr (ids[i], ==, bolt_domain_get_id (dom)); domains = bolt_domain_insert (domains, dom); all[i] = dom; g_object_add_weak_pointer (G_OBJECT (dom), (gpointer *) &all[i]); } g_assert_nonnull (domains); g_assert_cmpuint (bolt_domain_count (domains), ==, G_N_ELEMENTS (sl)); g_assert_cmpint (bolt_sysfs_count_hosts (tt->udev, NULL), ==, (int) G_N_ELEMENTS (sl)); n = 0; bolt_domain_foreach (domains, count_domains, &n); g_assert_cmpint (n, ==, (int) G_N_ELEMENTS (sl)); iter = domains; for (gsize i = 0; i < bolt_domain_count (domains); i++) { const char *id = bolt_domain_get_id (iter); BoltSecurity s = bolt_domain_get_security (iter); g_assert_cmpstr (id, ==, ids[i]); g_assert_cmpint (s, ==, sl[i]); iter = bolt_domain_next (iter); } iter = bolt_domain_prev (domains); g_assert_cmpstr (bolt_domain_get_id (iter), ==, ids[G_N_ELEMENTS (sl) - 1]); /* removing of domains: start with the second one */ iter = bolt_domain_next (domains); domains = bolt_domain_remove (domains, iter); g_assert_cmpuint (bolt_domain_count (domains), ==, G_N_ELEMENTS (sl) - 1); g_assert_cmpstr (bolt_domain_get_id (domains), ==, ids[0]); iter = bolt_domain_next (domains); /* ids[1] should be gone */ g_assert_cmpstr (bolt_domain_get_id (iter), ==, ids[2]); /* remove of domains: the list head */ domains = bolt_domain_remove (domains, domains); g_assert_cmpuint (bolt_domain_count (domains), ==, G_N_ELEMENTS (sl) - 2); /* the head is now ids[2], because 0, 1 got removed */ g_assert_cmpstr (bolt_domain_get_id (domains), ==, ids[2]); /* remove of domains: clear the whole list */ bolt_domain_clear (&domains); g_assert_null (domains); g_assert_cmpuint (bolt_domain_count (domains), ==, 0); /* check we also got rid of all references */ for (gsize i = 0; i < G_N_ELEMENTS (all); i++) g_assert_null (all[i]); } static void test_sysfs_domain_connect (TestSysfs *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; g_autoptr(BoltDomain) domain = NULL; const char *uid = "884c6edd-7118-4b21-b186-b02d396ecca0"; const char *id; const char *syspath; BoltSecurity security; domain = g_object_new (BOLT_TYPE_DOMAIN, "store", NULL, "uid", uid, "bootacl", NULL, NULL); g_assert_nonnull (domain); g_assert_false (bolt_domain_has_iommu (domain)); security = bolt_domain_get_security (domain); g_assert_cmpint (security, ==, BOLT_SECURITY_UNKNOWN); g_assert_null (bolt_domain_get_syspath (domain)); id = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, "iommu", "1\n", NULL); syspath = mock_sysfs_domain_get_syspath (tt->sysfs, id); udevice = udev_device_new_from_syspath (tt->udev, syspath); g_assert_nonnull (udevice); bolt_domain_connected (domain, udevice); security = bolt_domain_get_security (domain); g_assert_cmpint (security, ==, BOLT_SECURITY_SECURE); g_assert_cmpstr (syspath, ==, bolt_domain_get_syspath (domain)); g_assert_true (bolt_domain_has_iommu (domain)); } typedef struct { MockSysfs *sysfs; struct udev *udev; GStrv acl; guint slots; const char *dom_sysid; const char *dom_uid; BoltDomain *dom; } TestBootacl; static void test_bootacl_setup (TestBootacl *tt, gconstpointer data) { g_autoptr(GError) err = NULL; g_autoptr(udev_device) udevice = NULL; g_auto(GStrv) have = NULL; g_autofree char *str = NULL; const char *syspath; static const char *uid = "884c6edd-7118-4b21-b186-b02d396ecca0"; tt->sysfs = mock_sysfs_new (); tt->udev = udev_new (); tt->slots = 16; str = g_strnfill (tt->slots - 1, ','); tt->acl = g_strsplit (str, ",", 1024); tt->dom_sysid = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_USER, "bootacl", tt->acl, NULL); syspath = mock_sysfs_domain_get_syspath (tt->sysfs, tt->dom_sysid); udevice = udev_device_new_from_syspath (tt->udev, syspath); tt->dom_uid = uid; tt->dom = bolt_domain_new_for_udev (udevice, tt->dom_uid, &err); g_assert_no_error (err); g_assert_nonnull (tt->dom); g_assert_cmpstr (bolt_domain_get_uid (tt->dom), ==, uid); g_assert_true (bolt_domain_supports_bootacl (tt->dom)); g_object_get (tt->dom, "bootacl", &have, NULL); g_assert_nonnull (have); bolt_assert_strv_equal (have, tt->acl, -1); } static void test_bootacl_tear_down (TestBootacl *tt, gconstpointer user) { g_clear_pointer (&tt->acl, g_strfreev); g_clear_object (&tt->dom); g_clear_object (&tt->sysfs); g_clear_pointer (&tt->udev, udev_unref); } static void dump_strv (GStrv strv, const char *prefix) { if (strv == NULL) { g_print ("%s is NULL\n", prefix); return; } else if (*strv == NULL) { g_print ("%s is EMPTY\n", prefix); return; } for (guint i = 0; strv[i]; i++) g_print ("%s[%u] %s\n", prefix, i, strv[i]); } static void test_bootacl_connect_domain (TestBootacl *tt, BoltDomain *dom) { g_autoptr(udev_device) udevice = NULL; const char *syspath; syspath = mock_sysfs_domain_get_syspath (tt->sysfs, tt->dom_sysid); udevice = udev_device_new_from_syspath (tt->udev, syspath); bolt_domain_connected (dom, udevice); } static void test_bootacl_read_acl (TestBootacl *tt, GStrv *acl) { g_autoptr(GError) err = NULL; g_clear_pointer (acl, g_strfreev); *acl = mock_sysfs_domain_bootacl_get (tt->sysfs, tt->dom_sysid, &err); g_assert_no_error (err); g_assert_nonnull (*acl); } static void test_bootacl_write_acl (TestBootacl *tt, GStrv acl) { g_autoptr(GError) err = NULL; gboolean ok; ok = mock_sysfs_domain_bootacl_set (tt->sysfs, tt->dom_sysid, acl, &err); g_assert_no_error (err); g_assert_true (ok); } static void test_bootacl_connect_and_verify (TestBootacl *tt, BoltDomain *dom, GStrv *acl) { g_auto(GStrv) sysacl = NULL; GStrv have = NULL; test_bootacl_connect_domain (tt, dom); test_bootacl_read_acl (tt, &sysacl); dump_strv (sysacl, "sysacl "); dump_strv (tt->acl, "acl "); /* the domain and sysfs */ have = bolt_domain_get_bootacl (dom); bolt_assert_strv_equal (have, sysacl, -1); /* the domain and what we expect */ bolt_assert_strv_equal (have, tt->acl, -1); if (acl != NULL) bolt_swap (*acl, sysacl); } static void test_bootacl_add_uuid (TestBootacl *tt, BoltDomain *dom, int slot, const char *uuidfmt, ...) G_GNUC_PRINTF (4, 5); static void test_bootacl_add_uuid (TestBootacl *tt, BoltDomain *dom, int slot, const char *uuidfmt, ...) { g_autoptr(GError) err = NULL; g_autofree char *uuid = NULL; GStrv acl = tt->acl; gboolean ok; va_list args; va_start (args, uuidfmt); uuid = g_strdup_vprintf (uuidfmt, args); ok = bolt_domain_bootacl_add (dom, uuid, &err); va_end (args); g_assert_no_error (err); g_assert_true (ok); g_assert_true (bolt_domain_bootacl_contains (dom, uuid)); if (slot > -1) bolt_swap (acl[slot], uuid); } static void test_bootacl_del_uuid (TestBootacl *tt, BoltDomain *dom, const char *uuid) { g_autoptr(GError) err = NULL; GStrv acl = tt->acl; gboolean ok; char **p; ok = bolt_domain_bootacl_del (dom, uuid, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (bolt_domain_bootacl_contains (dom, uuid)); p = bolt_strv_contains (acl, uuid); if (p != NULL) bolt_set_strdup (p, ""); } /* the bootacl related tests */ static void test_bootacl_basic (TestBootacl *tt, gconstpointer user) { g_auto(GStrv) sysacl = NULL; g_autofree const char **used = NULL; BoltDomain *dom = tt->dom; guint slots = tt->slots; guint n, n_free, n_used; g_assert_true (bolt_domain_supports_bootacl (dom)); n = bolt_domain_bootacl_slots (dom, &n_free); g_assert_cmpuint (n, ==, slots); g_assert_cmpuint (n_free, ==, slots); used = bolt_domain_bootacl_get_used (dom, &n_used); g_assert_cmpuint (g_strv_length ((GStrv) used), ==, 0); g_assert_cmpuint (n_used, ==, 0); /* disconnect and reconnect */ bolt_domain_disconnected (dom); test_bootacl_connect_and_verify (tt, dom, &sysacl); /* simulate some pathological cases that should not * actually happen, but should still be handled */ /* after we connect, the slot list is empty */ bolt_domain_disconnected (dom); g_strfreev (tt->acl); tt->acl = g_strsplit ("", ",", -1); test_bootacl_write_acl (tt, tt->acl); test_bootacl_connect_and_verify (tt, dom, &sysacl); g_assert_false (bolt_domain_supports_bootacl (dom)); n = bolt_domain_bootacl_slots (dom, &n_free); g_assert_cmpuint (n, ==, 0); g_assert_cmpuint (n_free, ==, 0); /* after we connect, the slot list changed */ bolt_domain_disconnected (dom); g_strfreev (tt->acl); tt->acl = g_strsplit (",", ",", -1); /* two slots */ test_bootacl_write_acl (tt, tt->acl); test_bootacl_connect_and_verify (tt, dom, &sysacl); g_assert_true (bolt_domain_supports_bootacl (dom)); n = bolt_domain_bootacl_slots (dom, &n_free); g_assert_cmpuint (n, ==, 2); g_assert_cmpuint (n_free, ==, 2); } static void test_bootacl_errors (TestBootacl *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; g_autoptr(BoltDomain) dom2 = NULL; g_autoptr(GError) err = NULL; g_auto(GStrv) tmp = NULL; g_autofree char *str = NULL; BoltDomain *dom = tt->dom; const char *noacl_dom; const char *syspath; GStrv acl = tt->acl; gboolean ok; /* adding an existing uuid */ test_bootacl_add_uuid (tt, dom, 0, "deadbab%x-0200-0100-ffff-ffffffffffff", 0U); ok = bolt_domain_bootacl_add (dom, acl[0], &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_EXISTS); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* removing an unknown uuid */ ok = bolt_domain_bootacl_del (dom, "deadbabe-0200-ffff-ffff-ffffffffffff", &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* number of slots mismatch */ str = g_strnfill (tt->slots * 2, ','); tmp = g_strsplit (str, ",", 1024); ok = bolt_domain_bootacl_set (dom, tmp, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_clear_pointer (&err, g_error_free); /* domain without bootacl support */ noacl_dom = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); g_assert_nonnull (noacl_dom); syspath = mock_sysfs_domain_get_syspath (tt->sysfs, noacl_dom); udevice = udev_device_new_from_syspath (tt->udev, syspath); dom2 = bolt_domain_new_for_udev (udevice, tt->dom_uid, &err); g_assert_no_error (err); g_assert_nonnull (dom2); g_assert_false (bolt_domain_supports_bootacl (dom2)); ok = bolt_domain_bootacl_add (dom2, acl[0], &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); ok = bolt_domain_bootacl_del (dom2, acl[0], &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); ok = bolt_domain_bootacl_set (dom2, tmp, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_false (ok); g_clear_pointer (&err, g_error_free); } static void on_bootacl_notify (GObject *object, GParamSpec *pspec, gpointer user_data) { guint *n_signals = user_data; (*n_signals)++; } typedef struct { gboolean changed; GHashTable *changes; gboolean fired; } AclChangeSet; #define ACL_CHANGE_SET_INIT {FALSE, NULL, FALSE, } static void acl_change_set_clear (AclChangeSet *set) { g_clear_pointer (&set->changes, g_hash_table_unref); set->changed = FALSE; set->fired = FALSE; } G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (AclChangeSet, acl_change_set_clear); static void acl_change_set_verify (AclChangeSet *cs, int n_changes, ...) { guint sz; va_list ap; if (n_changes == -1) { g_assert_false (cs->fired); return; } g_assert_true (cs->fired); g_assert_true (cs->changed); sz = g_hash_table_size (cs->changes); g_assert_cmpint (sz, ==, n_changes); va_start (ap, n_changes); for (int i = 0; i < n_changes; i++) { const char *uuid = va_arg (ap, const char *); int op = va_arg (ap, int); char *key; g_assert_nonnull (uuid); key = g_hash_table_lookup (cs->changes, uuid); g_assert_nonnull (key); g_assert_cmpint (GPOINTER_TO_INT (key), ==, op); } va_end (ap); } static void on_bootacl_changed (BoltDomain *dom, gboolean changed, GHashTable *changes, gpointer user_data) { AclChangeSet *changeset = user_data; acl_change_set_clear (changeset); changeset->changed = changed; changeset->changes = g_hash_table_ref (changes); changeset->fired = TRUE; } static void test_bootacl_update_udev (TestBootacl *tt, gconstpointer user) { g_auto(AclChangeSet) changeset = ACL_CHANGE_SET_INIT; GStrv acl = tt->acl; BoltDomain *dom = tt->dom; guint slots = tt->slots; const char *syspath; guint n_free, n_used; guint n_signals = 0; syspath = mock_sysfs_domain_get_syspath (tt->sysfs, tt->dom_sysid); /* fill in some uuids via sysfs */ g_signal_connect (dom, "notify::bootacl", G_CALLBACK (on_bootacl_notify), &n_signals); g_signal_connect (dom, "bootacl-changed", G_CALLBACK (on_bootacl_changed), &changeset); for (guint i = 0; i < 8; i++) { g_autoptr(udev_device) ud = NULL; GStrv have = NULL; g_autofree const char **used = NULL; char *uuid = NULL; struct udev *udev; guint n; uuid = g_strdup_printf ("deadbab%x-0200-0100-ffff-ffffffffffff", i); bolt_set_str (&acl[i], uuid); test_bootacl_write_acl (tt, acl); udev = udev_new (); ud = udev_device_new_from_syspath (udev, syspath); bolt_domain_update_from_udev (dom, ud); g_assert_cmpuint (n_signals, ==, i + 1); g_assert_true (bolt_domain_bootacl_contains (dom, acl[i])); n = bolt_domain_bootacl_slots (dom, &n_free); g_assert_cmpuint (n, ==, slots); g_assert_cmpuint (n_free, ==, slots - (i + 1)); used = bolt_domain_bootacl_get_used (dom, &n_used); g_assert_cmpuint (n_used, ==, i + 1); g_assert_nonnull (used); have = bolt_domain_get_bootacl (dom); bolt_assert_strv_equal (have, acl, -1); /* we verify the bootacl-changed signal */ acl_change_set_verify (&changeset, 1, uuid, '+'); changeset.fired = FALSE; udev_unref (udev); } } static void test_bootacl_update_online (TestBootacl *tt, gconstpointer user) { g_autoptr(GError) err = NULL; g_auto(AclChangeSet) changeset = ACL_CHANGE_SET_INIT; g_auto(GStrv) sysacl = NULL; GStrv acl = tt->acl; BoltDomain *dom = tt->dom; gboolean ok; guint slots; g_signal_connect (dom, "bootacl-changed", G_CALLBACK (on_bootacl_changed), &changeset); for (guint i = 0; i < tt->slots; i++) { test_bootacl_add_uuid (tt, dom, i, "deadbab%x-0200-0100-ffff-ffffffffffff", i); test_bootacl_read_acl (tt, &sysacl); g_assert_nonnull (bolt_strv_contains (sysacl, acl[i])); acl_change_set_verify (&changeset, 1, acl[i], '+'); changeset.fired = FALSE; bolt_assert_strv_equal (acl, sysacl, -1); } /* verify with what we have in mock sysfs */ test_bootacl_read_acl (tt, &sysacl); slots = bolt_strv_length (acl); dump_strv (sysacl, "sysacl "); /* NB: acl was verified to be in sync with domain's acl */ bolt_assert_strv_equal (acl, sysacl, -1); /* lets overwrite all the bootacl entries bit by bit * and also verify we honor FIFO when replacing them */ for (guint i = 0; i < tt->slots; i++) { g_auto(GStrv) have = NULL; /* NB: different uuid pattern from above (0200-0100) */ test_bootacl_add_uuid (tt, dom, i, "deadbab%x-0200-aaaa-ffff-ffffffffffff", i); test_bootacl_read_acl (tt, &have); g_assert_nonnull (bolt_strv_contains (have, acl[i])); g_assert_cmpstr (have[slots - 1], ==, acl[i]); /* check the bootacl-changed signal emission: * add for the new one, remove for the overwritten one */ acl_change_set_verify (&changeset, 2, acl[i], '+', sysacl[i], '-'); changeset.fired = FALSE; } /* remove all the entries */ test_bootacl_read_acl (tt, &sysacl); slots = bolt_strv_length (acl); dump_strv (sysacl, "sysacl "); for (guint i = 0; i < slots; i++) { g_auto(GStrv) have = NULL; char *uuid = sysacl[i]; changeset.fired = FALSE; test_bootacl_del_uuid (tt, dom, uuid); test_bootacl_read_acl (tt, &have); g_assert_null (bolt_strv_contains (have, uuid)); /* check the bootacl-changed signal emission */ acl_change_set_verify (&changeset, 1, uuid, '-'); changeset.fired = FALSE; } /* now we set a bunch in one-go */ for (guint i = 0; i < tt->slots; i++) bolt_set_strdup_printf (&acl[i], "deadbab%x-cccc-0100-ffff-ffffffffffff", i); changeset.fired = FALSE; ok = bolt_domain_bootacl_set (dom, acl, &err); g_assert_no_error (err); g_assert_true (ok); /* check we got removed signals for all of them */ g_assert_true (changeset.fired); g_assert_true (changeset.changed); g_assert_cmpuint (g_hash_table_size (changeset.changes), ==, slots); changeset.fired = FALSE; dump_strv (sysacl, "sysacl "); test_bootacl_read_acl (tt, &sysacl); bolt_assert_strv_equal (acl, sysacl, -1); /* check that if we set the same bootacl as * we already have, we get FALSE but no error */ changeset.fired = FALSE; ok = bolt_domain_bootacl_set (dom, acl, &err); g_assert_no_error (err); g_assert_false (ok); g_assert_false (changeset.fired); } static void test_bootacl_update_offline (TestBootacl *tt, gconstpointer user) { g_autoptr(GError) err = NULL; g_autoptr(BoltStore) store = NULL; g_auto(GStrv) sysacl = NULL; g_auto(BoltTmpDir) dir = NULL; BoltDomain *dom = tt->dom; gboolean ok; GStrv have; GStrv acl = tt->acl; guint k; dir = bolt_tmp_dir_make ("bolt.sysfs.XXXXXX", NULL); store = bolt_store_new (dir, &err); g_assert_no_error (err); g_assert_nonnull (store); ok = bolt_store_put_domain (store, dom, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (bolt_domain_is_stored (dom)); g_assert_true (bolt_domain_supports_bootacl (dom)); /* 1. disconnect and add uuids that will get added to the journal */ g_debug ("1. adding uuids offline"); bolt_domain_disconnected (dom); for (guint i = 0; i < tt->slots / 2; i++) test_bootacl_add_uuid (tt, dom, i, "deadbab%x-0200-0100-ffff-ffffffffffff", i); have = bolt_domain_get_bootacl (dom); bolt_assert_strv_equal (have, acl, -1); /* connect, and make sure we have sync */ test_bootacl_connect_and_verify (tt, dom, &sysacl); /* 2. disconnect and remove uuids so they will end up in the journal */ /* remove the first quarter of uuids */ g_debug ("2. remove uuids offline"); bolt_domain_disconnected (dom); for (guint i = 0; i < tt->slots / 4; i++) test_bootacl_del_uuid (tt, dom, acl[i]); /* simulate external changes: uuids added at the end */ for (guint i = tt->slots / 2 + 1; i < tt->slots; i++) { bolt_set_strdup_printf (&sysacl[i], "deadbab%x-0200-0100-ffff-ffffffffffff", i); bolt_set_strdup_printf (&acl[i], "deadbab%x-0200-0100-ffff-ffffffffffff", i); } /* write the external modifications */ test_bootacl_write_acl (tt, sysacl); /* connect, and make sure we have sync */ test_bootacl_connect_and_verify (tt, dom, &sysacl); /* 3. simulate external modifications on top of journaled changes */ g_debug ("3. external updates and offline changes"); bolt_domain_disconnected (dom); dump_strv (bolt_domain_get_bootacl (dom), "domain "); /* current state: [0, N/4]: empty * [N/4, N]: filled */ test_bootacl_read_acl (tt, &sysacl); /* [ 0 ] externally added and added in the journal (duplicated) */ k = 0; bolt_set_strdup_printf (&sysacl[k], "deadbab%x-0200-0100-ffff-ffffffffffff", k); test_bootacl_add_uuid (tt, dom, k, "deadbab%x-0200-0100-ffff-ffffffffffff", k); /* [ 1 ] added via the journal */ k = 1; test_bootacl_add_uuid (tt, dom, k, "deadbab%x-0200-0100-ffff-ffffffffffff", k); bolt_set_strdup_printf (&acl[k], "deadbab%x-0200-0100-ffff-ffffffffffff", k); /* [N/2+1] removed externally and via the journal */ k = tt->slots / 2 + 1; bolt_set_strdup (&sysacl[k], ""); test_bootacl_del_uuid (tt, dom, acl[k]); /* [N/2+2] removed via the journal */ k = tt->slots / 2 + 2; test_bootacl_del_uuid (tt, dom, acl[k]); /* write the external modifications */ test_bootacl_write_acl (tt, sysacl); /* connect, and make sure we have sync */ test_bootacl_connect_and_verify (tt, dom, &sysacl); /* 4. we pretend we got disconnected and reconnected with no change */ g_debug ("4. no change reconnect"); bolt_domain_disconnected (dom); test_bootacl_read_acl (tt, &sysacl); } static gboolean bootacl_allocator (BoltDomain *domain, GStrv bootacl, const char *uid, gint *slot, gpointer data) { g_assert_nonnull (bootacl); g_assert_nonnull (uid); g_assert_nonnull (slot); g_assert_cmpint (*slot, >, -1); *slot = 0; return TRUE; } static void test_bootacl_allocate (TestBootacl *tt, gconstpointer user) { GStrv acl = tt->acl; BoltDomain *dom = tt->dom; g_signal_connect (dom, "bootacl-alloc", G_CALLBACK (bootacl_allocator), NULL); for (guint i = 0; i < tt->slots; i++) { g_auto(GStrv) have = NULL; GStrv domacl; test_bootacl_add_uuid (tt, dom, 0, "deadbab%x-0200-0100-ffff-ffffffffffff", i); test_bootacl_read_acl (tt, &have); g_assert_cmpstr (have[0], ==, acl[0]); bolt_assert_strv_equal (acl, have, -1); domacl = bolt_domain_get_bootacl (dom); bolt_assert_strv_equal (domacl, have, -1); } } static void test_check_kernel_version (TestSysfs *tt, gconstpointer user) { gboolean ok; /* simulate read errors */ ok = mock_sysfs_set_osrelease (tt->sysfs, NULL); g_assert_true (ok); g_assert_false (bolt_check_kernel_version (1, 0)); /* short kernel version */ ok = mock_sysfs_set_osrelease (tt->sysfs, "1.0"); g_assert_true (ok); g_assert_true (bolt_check_kernel_version (1, 0)); g_assert_false (bolt_check_kernel_version (1, 1)); g_assert_false (bolt_check_kernel_version (2, 0)); /* more realistic kernel version */ ok = mock_sysfs_set_osrelease (tt->sysfs, "1.0.0-111.fc1"); g_assert_true (ok); g_assert_true (bolt_check_kernel_version (1, 0)); g_assert_false (bolt_check_kernel_version (1, 1)); g_assert_false (bolt_check_kernel_version (2, 0)); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/sysfs/device_get_unique_id", TestSysfs, NULL, test_sysfs_setup, test_sysfs_device_get_unique_id, test_sysfs_tear_down); g_test_add ("/sysfs/device_ident", TestSysfs, NULL, test_sysfs_setup, test_sysfs_device_ident, test_sysfs_tear_down); g_test_add ("/sysfs/host_ident", TestSysfs, NULL, test_sysfs_setup, test_sysfs_host_ident, test_sysfs_tear_down); g_test_add ("/sysfs/domain_for_device", TestSysfs, NULL, test_sysfs_setup, test_sysfs_domain_for_device, test_sysfs_tear_down); g_test_add ("/sysfs/info_for_device", TestSysfs, NULL, test_sysfs_setup, test_sysfs_info_for_device, test_sysfs_tear_down); g_test_add ("/sysfs/domain_get_nhi_id", TestSysfs, NULL, test_sysfs_setup, test_sysfs_nhi_id_for_domain, test_sysfs_tear_down); g_test_add ("/sysfs/nhi_uuid_is_stable", TestSysfs, NULL, test_sysfs_setup, test_nhi_uuid_is_stable, test_sysfs_tear_down); g_test_add ("/sysfs/read_iommu", TestSysfs, NULL, test_sysfs_setup, test_sysfs_read_iommu, test_sysfs_tear_down); g_test_add ("/sysfs/domain/basic", TestSysfs, NULL, test_sysfs_setup, test_sysfs_domains, test_sysfs_tear_down); g_test_add ("/sysfs/domain/connect", TestSysfs, NULL, test_sysfs_setup, test_sysfs_domain_connect, test_sysfs_tear_down); g_test_add ("/sysfs/domain/bootacl/basic", TestBootacl, NULL, test_bootacl_setup, test_bootacl_basic, test_bootacl_tear_down); g_test_add ("/sysfs/domain/bootacl/errors", TestBootacl, NULL, test_bootacl_setup, test_bootacl_errors, test_bootacl_tear_down); g_test_add ("/sysfs/domain/bootacl/update_udev", TestBootacl, NULL, test_bootacl_setup, test_bootacl_update_udev, test_bootacl_tear_down); g_test_add ("/sysfs/domain/bootacl/update_online", TestBootacl, NULL, test_bootacl_setup, test_bootacl_update_online, test_bootacl_tear_down); g_test_add ("/sysfs/domain/bootacl/update_offline", TestBootacl, NULL, test_bootacl_setup, test_bootacl_update_offline, test_bootacl_tear_down); g_test_add ("/sysfs/domain/bootacl/allocate", TestBootacl, NULL, test_bootacl_setup, test_bootacl_allocate, test_bootacl_tear_down); g_test_add ("/self/check-kernel-version", TestSysfs, NULL, test_sysfs_setup, test_check_kernel_version, test_sysfs_tear_down); return g_test_run (); } bolt-0.9.2/tests/test-udev.c000066400000000000000000000145401417453051000157140ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-udev.h" #include "bolt-dbus.h" #include "bolt-str.h" #include "bolt-test.h" #include "mock-sysfs.h" #include #include #include #include #include #include typedef struct udev_device udev_device; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); typedef struct { MockSysfs *sysfs; struct udev *udev; } TestUdev; static void test_udev_setup (TestUdev *tt, gconstpointer data) { tt->sysfs = mock_sysfs_new (); tt->udev = udev_new (); } static void test_udev_tear_down (TestUdev *tt, gconstpointer user) { g_clear_object (&tt->sysfs); g_clear_pointer (&tt->udev, udev_unref); } typedef struct { char *action; udev_device *dev; gint have; /* */ GMainLoop *loop; /* conditions */ gint should; gboolean timedout; } UEvent; static void uevent_clear (UEvent *ev) { g_clear_pointer (&ev->dev, udev_device_unref); g_clear_pointer (&ev->action, g_free); g_clear_pointer (&ev->loop, g_main_loop_unref); } static void got_uevent (BoltUdev *udev, const char *action, struct udev_device *device, gpointer user_data) { UEvent *ev = user_data; gboolean quit = FALSE; bolt_set_strdup (&ev->action, action); g_clear_pointer (&ev->dev, udev_device_unref); ev->dev = udev_device_ref (device); ev->have++; if (ev->should > 0) quit = (--ev->should) == 0; if (quit) g_main_loop_quit (ev->loop); } static gboolean got_timeout (gpointer user_data) { UEvent *ev = user_data; ev->timedout = TRUE; g_main_loop_quit (ev->loop); return FALSE; } static gint wait_for_event (UEvent *ev, guint timeout) { guint tid; ev->timedout = FALSE; tid = g_timeout_add_seconds (timeout, got_timeout, ev); if (ev->loop == NULL) ev->loop = g_main_loop_new (NULL, FALSE); ev->should = 1; ev->have = 0; g_main_loop_run (ev->loop); if (!ev->timedout) g_source_remove (tid); return ev->have; } static void test_udev_basic (TestUdev *tt, gconstpointer user) { g_auto(GStrv) prop_filter = NULL; g_autoptr(GError) err = NULL; g_autoptr(BoltUdev) udev = NULL; struct udev_device *dev = NULL; g_autofree char *prop_name = NULL; g_autofree char *idstr = NULL; UEvent ev = { NULL, }; const char *filter[] = {"thunderbolt", NULL}; const char *syspath; const char *domain; const char *name; gint n; udev = bolt_udev_new ("udev", filter, &err); g_assert_nonnull (udev); g_assert_no_error (err); g_object_get (udev, "name", &prop_name, "filter", &prop_filter, NULL); g_assert_cmpstr (prop_name, ==, "udev"); bolt_assert_strv_equal ((const GStrv) filter, prop_filter, -1); g_signal_connect (udev, "uevent", (GCallback) got_uevent, &ev); /* add a domain */ domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_NONE, NULL); n = wait_for_event (&ev, 2); g_assert_false (ev.timedout); g_assert_cmpint (n, ==, 1); g_assert_cmpstr (ev.action, ==, "add"); g_assert_nonnull (ev.dev); name = udev_device_get_sysname (ev.dev); g_assert_cmpstr (domain, ==, name); /* test we can create a valid udev device */ syspath = udev_device_get_syspath (ev.dev); dev = bolt_udev_device_new_from_syspath (udev, syspath, &err); g_assert_nonnull (dev); g_assert_no_error (err); /* remove a domain */ idstr = g_strdup (domain); mock_sysfs_domain_remove (tt->sysfs, idstr); n = wait_for_event (&ev, 2); g_assert_false (ev.timedout); g_assert_cmpint (n, ==, 1); g_assert_cmpstr (ev.action, ==, "remove"); g_assert_nonnull (ev.dev); name = udev_device_get_sysname (ev.dev); g_assert_cmpstr (idstr, ==, name); /* cleanup */ uevent_clear (&ev); udev_device_unref (dev); } static void test_udev_detect_force_power (TestUdev *tt, gconstpointer user) { g_autoptr(GError) err = NULL; g_autoptr(BoltUdev) udev = NULL; g_autofree char *path = NULL; const char *fp; gboolean ok; udev = bolt_udev_new ("udev", NULL, &err); g_assert_no_error (err); g_assert_nonnull (udev); /* no force power module attached so far */ ok = bolt_udev_detect_force_power (udev, &path, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_null (path); /* now we add the wmi module */ fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); ok = bolt_udev_detect_force_power (udev, &path, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_nonnull (path); g_debug ("force power detected at: %s", path); g_clear_pointer (&path, g_free); /* unload again */ g_debug ("UNLOAD"); mock_sysfs_force_power_unload (tt->sysfs); ok = bolt_udev_detect_force_power (udev, &path, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_null (path); /* and load again */ g_debug ("LOAD"); mock_sysfs_force_power_load (tt->sysfs); ok = bolt_udev_detect_force_power (udev, &path, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_nonnull (path); g_debug ("force power detected at: %s", path); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); bolt_dbus_ensure_resources (); g_test_add ("/udev/basic", TestUdev, NULL, test_udev_setup, test_udev_basic, test_udev_tear_down); g_test_add ("/udev/detect_force_power", TestUdev, NULL, test_udev_setup, test_udev_detect_force_power, test_udev_tear_down); return g_test_run (); } bolt-0.9.2/tests/test-unix.c000066400000000000000000000122631417453051000157340ustar00rootroot00000000000000/* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-unix.h" #include "bolt-io.h" #include "bolt-macros.h" #include "bolt-names.h" #include "bolt-str.h" #include "bolt-test.h" #include #include #include #include #include #include /* waitpid */ #include /* fork */ typedef struct TestDummy { int dummy; } TestDummy; static void test_pid_is_alive (TestDummy *tt, gconstpointer user_data) { gboolean ok; pid_t p; pid_t r; int status; ok = bolt_pid_is_alive (0); g_assert_true (ok); p = fork (); g_assert_cmpint ((int) p, >, -1); if (p == 0) /* child */ exit (42); /* parent */ ok = bolt_pid_is_alive (p); g_assert_true (ok); r = waitpid (0, &status, 0); g_assert_cmpint ((int) r, ==, (int) p); ok = bolt_pid_is_alive (p); g_assert_false (ok); } typedef struct TestNotify { NotifySocket *ns; } TestNotify; static void test_notify_setup (TestNotify *tt, gconstpointer data) { tt->ns = notify_socket_new (); } static void test_notify_teardown (TestNotify *tt, gconstpointer user) { g_clear_pointer (&tt->ns, notify_socket_free); } static void test_sd_notify (TestNotify *tt, gconstpointer user) { g_autoptr(GError) err = NULL; g_autofree char *verylong = NULL; const char *ref = NULL; gboolean sent; gboolean ok; size_t l; char *msg; /* no socket at all */ ref = "STATUS=this is my message"; ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_false (sent); /* invalid/unsupported destinations */ g_setenv (BOLT_SD_NOTIFY_SOCKET, "INVALID SOCKET", TRUE); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_false (ok); g_assert_false (sent); g_clear_error (&err); /* socket dest too long */ l = sizeof (((struct sockaddr_un *) 0)->sun_path) + 10; g_assert_cmpuint (l, <, 1024); verylong = g_strnfill (l, 'a'); g_setenv (BOLT_SD_NOTIFY_SOCKET, verylong, TRUE); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); g_assert_false (sent); g_clear_error (&err); /* peer does not exist */ g_setenv (BOLT_SD_NOTIFY_SOCKET, "@NONEXISTANTABSTRACT", TRUE); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED); g_assert_false (ok); g_assert_true (sent); g_clear_error (&err); /* finally the VALID socket */ notify_socket_set_environment (tt->ns); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (sent); msg = notify_socket_revmsg (tt->ns, FALSE); g_assert_nonnull (msg); g_assert_cmpstr (msg, ==, ref); g_free (msg); } static void test_sd_watchdog_enabled (TestDummy *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autofree char *tmp = NULL; guint64 val; int r; /* no env variable */ g_assert_null (g_getenv (BOLT_SD_WATCHDOG_USEC)); r = bolt_sd_watchdog_enabled (&val, &err); g_assert_no_error (err); g_assert_cmpint (r, ==, 0); /* empty env variable [error] */ g_setenv (BOLT_SD_WATCHDOG_USEC, "", TRUE); r = bolt_sd_watchdog_enabled (&val, &err); g_assert_nonnull (err); g_assert_cmpint (r, <, 0); g_clear_error (&err); /* invalid env variable [error] */ g_setenv (BOLT_SD_WATCHDOG_USEC, "NOT-A-NUMBER", TRUE); r = bolt_sd_watchdog_enabled (&val, &err); g_assert_nonnull (err); g_assert_cmpint (r, <, 0); g_clear_error (&err); /* valid number, finally */ tmp = g_strdup_printf ("%d", 42 * G_USEC_PER_SEC); g_setenv (BOLT_SD_WATCHDOG_USEC, tmp, TRUE); r = bolt_sd_watchdog_enabled (&val, &err); g_assert_no_error (err); g_assert_cmpint (r, >, 0); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_test_add ("/common/unix/pid_is_alive", TestDummy, NULL, NULL, test_pid_is_alive, NULL); g_test_add ("/common/unix/bolt_sd_notify", TestNotify, NULL, test_notify_setup, test_sd_notify, test_notify_teardown); g_test_add ("/common/unix/sd_watchdog_enabled", TestDummy, NULL, NULL, test_sd_watchdog_enabled, NULL); return g_test_run (); } bolt-0.9.2/tests/test-watchdog.c000066400000000000000000000134771417453051000165610ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-watchdog.h" #include "bolt-names.h" #include "bolt-test.h" #include "bolt-time.h" #include typedef struct TestWatchdog { NotifySocket *ns; /* */ guint64 timeout; /* in usec */ char *timestr; /* */ GArray *pulses; /* */ GCancellable *cancel; } TestWatchdog; static void test_notify_setup (TestWatchdog *tt, gconstpointer data) { tt->ns = notify_socket_new (); g_assert_nonnull (tt->ns); tt->timeout = 6 * G_USEC_PER_SEC; tt->timestr = g_strdup_printf ("%" G_GUINT64_FORMAT, tt->timeout); tt->pulses = g_array_new (FALSE, FALSE, sizeof (guint64)); tt->cancel = g_cancellable_new (); } static void test_notify_teardown (TestWatchdog *tt, gconstpointer user) { g_clear_pointer (&tt->ns, notify_socket_free); g_clear_pointer (&tt->timestr, g_free); g_clear_pointer (&tt->pulses, g_array_unref); g_clear_object (&tt->cancel); } static void test_watchdog_basic (TestWatchdog *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(BoltWatchdog) dog = NULL; guint64 timeout = 0; guint pulse; /* watchdog env not set */ dog = bolt_watchdog_new (&err); g_assert_no_error (err); g_assert_nonnull (dog); g_object_get (dog, "timeout", &timeout, "pulse", &pulse, NULL); g_assert_cmpuint (timeout, ==, 0); g_assert_cmpuint (pulse, ==, 0); /* invalid watchdog env */ g_setenv (BOLT_SD_WATCHDOG_USEC, "INVALID", TRUE); g_clear_object (&dog); dog = bolt_watchdog_new (&err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_null (dog); g_clear_error (&err); g_clear_object (&dog); /* now with some actual valid socket, watchdog */ notify_socket_set_environment (tt->ns); g_setenv (BOLT_SD_WATCHDOG_USEC, tt->timestr, TRUE); g_clear_object (&dog); dog = bolt_watchdog_new (&err); g_assert_no_error (err); g_assert_nonnull (dog); g_object_get (dog, "timeout", &timeout, NULL); g_assert_cmpuint (timeout, ==, tt->timeout); g_object_get (dog, "pulse", &pulse, NULL); g_assert_cmpuint (pulse, ==, timeout / 2 / G_USEC_PER_SEC); } static gpointer test_watchdog_receiver (gpointer data) { TestWatchdog *tt = data; GPollFD fds[2]; gboolean ok; notify_socket_make_pollfd (tt->ns, &fds[0]); ok = g_cancellable_make_pollfd (tt->cancel, &fds[1]); g_assert_true (ok); while (!g_cancellable_is_cancelled (tt->cancel)) { const char *msg; guint64 now; gint r; r = g_poll (fds, G_N_ELEMENTS (fds), -1); g_assert_cmpint (r, >, -1); if (!fds[0].revents) continue; msg = notify_socket_revmsg (tt->ns, TRUE); if (!g_str_has_prefix (msg, "WATCHDOG")) continue; now = bolt_now_in_seconds (); g_array_append_val (tt->pulses, now); g_debug ("%" G_GUINT64_FORMAT ": pulse received", now); fds[0].revents = fds[1].revents = 0; } return NULL; } static gboolean on_timeout_warn_quit_loop (gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); return G_SOURCE_REMOVE; } static void test_watchdog_timeout (TestWatchdog *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; g_autoptr(BoltWatchdog) dog = NULL; g_autoptr(GMainLoop) loop = NULL; GThread *thread; guint64 timeout = 0; guint pulse; guint n = 10; skip_test_unless (g_test_slow (), "slow tests disabled"); notify_socket_set_environment (tt->ns); g_setenv (BOLT_SD_WATCHDOG_USEC, tt->timestr, TRUE); dog = bolt_watchdog_new (&err); g_assert_no_error (err); g_assert_nonnull (dog); g_object_get (dog, "timeout", &timeout, NULL); g_assert_cmpuint (timeout, ==, tt->timeout); g_object_get (dog, "pulse", &pulse, NULL); g_assert_cmpuint (pulse, ==, timeout / 2 / G_USEC_PER_SEC); thread = g_thread_new ("NotifySocket", test_watchdog_receiver, tt); loop = g_main_loop_new (NULL, FALSE); /* we wait for 11 pulses */ g_timeout_add_seconds (pulse * n + 1, on_timeout_warn_quit_loop, loop); /* now we wait */ g_main_loop_run (loop); /* stop the background thread */ g_cancellable_cancel (tt->cancel); /* we got here because the timeout kicked in */ g_assert_cmpuint (tt->pulses->len, >=, n); for (guint i = 1; i < n; i++) { guint64 before = g_array_index (tt->pulses, guint64, i - 1); guint64 after = g_array_index (tt->pulses, guint64, i); gint64 diff = after - before; g_debug ("%u - %u: %" G_GINT64_FORMAT "s", i, i - 1, diff); g_assert_cmpint (diff, <, tt->timeout / G_USEC_PER_SEC); } (void) g_thread_join (thread); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_test_add ("/boltd/watchdog/basic", TestWatchdog, NULL, test_notify_setup, test_watchdog_basic, test_notify_teardown); g_test_add ("/boltd/watchdog/timeout", TestWatchdog, NULL, test_notify_setup, test_watchdog_timeout, test_notify_teardown); return g_test_run (); } bolt-0.9.2/tests/test-wire.c000066400000000000000000000034701417453051000157170ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Christian J. Kellner */ #include "config.h" #include "bolt-wire.h" #include "bolt-error.h" #include "test-enums.h" #include "bolt-test-resources.h" #include #include #include #include typedef struct TestWire { int dummy; } TestWire; static void test_linkspeed_basic (TestWire *tt, gconstpointer data) { BoltLinkSpeed a = { .rx.speed = 10, .rx.lanes = 1, .tx.speed = 20, .tx.lanes = 2 }; BoltLinkSpeed b = { .rx.speed = 20, .rx.lanes = 2, .tx.speed = 10, .tx.lanes = 1 }; g_autofree BoltLinkSpeed *c = NULL; g_assert_false (bolt_link_speed_equal (&a, &b)); b = a; g_assert_true (bolt_link_speed_equal (&a, &b)); c = bolt_link_speed_copy (&a); g_assert_true (bolt_link_speed_equal (&a, c)); } int main (int argc, char **argv) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_test_add ("/common/linkseed/basic", TestWire, NULL, NULL, test_linkspeed_basic, NULL); return g_test_run (); } bolt-0.9.2/tests/tests.gresource.xml000066400000000000000000000002741417453051000175100ustar00rootroot00000000000000 example.bolt.xml