├── .dir-locals.el ├── .github └── workflows │ ├── build.yml │ ├── codeql.yml │ └── install.yml ├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── NEWS ├── README.md ├── docs ├── .gitignore ├── conf.py ├── daemon.rst ├── default.css ├── id128.rst ├── index.rst ├── journal.rst ├── layout.html └── login.rst ├── pytest.ini ├── setup.py ├── systemd ├── .gitignore ├── __init__.py ├── _daemon.c ├── _journal.c ├── _reader.c ├── daemon.py ├── id128-constants.h ├── id128-defines.h ├── id128.c ├── journal.py ├── login.c ├── macro.h ├── pyutil.c ├── pyutil.h ├── strv.c ├── strv.h ├── test │ ├── test_daemon.py │ ├── test_id128.py │ ├── test_journal.py │ └── test_login.py ├── util.c └── util.h └── update-constants.py /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ; Sets emacs variables based on mode. 2 | ; A list of (major-mode . ((var1 . value1) (var2 . value2))) 3 | ; Mode can be nil, which gives default values. 4 | 5 | ((nil . ((indent-tabs-mode . nil) 6 | (c-basic-offset . 4))) 7 | ) 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vi: ts=2 sw=2 et: 3 | # SPDX-License-Identifier: LGPL-2.1-or-later 4 | # 5 | name: Build test 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-20.04 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ matrix.python }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | python: [ 25 | "3.7", 26 | "3.8", 27 | "3.9", 28 | "3.10", 29 | "3.11.0-rc.1", 30 | ] 31 | name: Python ${{ matrix.python }} 32 | steps: 33 | - name: Repository checkout 34 | uses: actions/checkout@v2 35 | 36 | - name: Configure Python ${{ matrix.python }} 37 | uses: actions/setup-python@v2 38 | with: 39 | python-version: ${{ matrix.python }} 40 | architecture: x64 41 | 42 | - name: Install dependencies 43 | run: | 44 | sudo apt -y update 45 | sudo apt -y install gcc libsystemd-dev 46 | python -m pip install pytest sphinx 47 | 48 | - name: Build (Python ${{ matrix.python }}) 49 | run: | 50 | set -x 51 | make -j 52 | make doc SPHINXOPTS="-W -v" 53 | 54 | - name: Test (Python ${{ matrix.python }}) 55 | run: | 56 | make check 57 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vi: ts=2 sw=2 et: 3 | # SPDX-License-Identifier: LGPL-2.1-or-later 4 | # 5 | name: "CodeQL" 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | analyze: 20 | name: Analyze 21 | runs-on: ubuntu-22.04 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ matrix.language }}-${{ github.ref }} 24 | cancel-in-progress: true 25 | permissions: 26 | actions: read 27 | security-events: write 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: ['cpp', 'python'] 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v3 37 | 38 | - name: Initialize CodeQL 39 | uses: github/codeql-action/init@v2 40 | with: 41 | languages: ${{ matrix.language }} 42 | queries: +security-extended,security-and-quality 43 | 44 | - name: Install dependencies 45 | run: | 46 | sudo apt -y update 47 | sudo apt -y install gcc libsystemd-dev 48 | 49 | - name: Autobuild 50 | uses: github/codeql-action/autobuild@v2 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@v2 54 | -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vi: ts=2 sw=2 et: 3 | # SPDX-License-Identifier: LGPL-2.1-or-later 4 | # 5 | name: Install test 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ matrix.container }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | container: [ 25 | "archlinux:latest", 26 | "debian:testing", 27 | "quay.io/centos/centos:stream8", 28 | "quay.io/fedora/fedora:rawhide", 29 | "ubuntu:focal", 30 | ] 31 | container: 32 | image: ${{ matrix.container }} 33 | name: ${{ matrix.container }} 34 | steps: 35 | - name: Repository checkout 36 | uses: actions/checkout@v2 37 | 38 | - name: Install dependencies 39 | shell: bash 40 | run: | 41 | DIST_ID="$(sed -nr 's/^ID="?(\w+)"?/\1/p' /etc/os-release)" 42 | echo "Distribution ID: $DIST_ID" 43 | 44 | DEPS_COMMON=( 45 | gcc 46 | git 47 | pkg-config 48 | python3 49 | systemd 50 | ) 51 | 52 | case "$DIST_ID" in 53 | arch) 54 | pacman --noconfirm -Sy "${DEPS_COMMON[@]}" systemd-libs 55 | python3 -m ensurepip 56 | ;; 57 | centos|fedora) 58 | dnf -y install "${DEPS_COMMON[@]}" systemd-devel python3-devel python3-pip 59 | ;; 60 | ubuntu|debian) 61 | apt -y update 62 | DEBIAN_FRONTEND=noninteractive apt -y install "${DEPS_COMMON[@]}" libsystemd-dev python3-dev python3-pip 63 | ;; 64 | *) 65 | echo >&2 "Invalid distribution ID: $DISTRO_ID" 66 | exit 1 67 | esac 68 | 69 | python3 -m pip install pytest sphinx 70 | 71 | - name: Build & install 72 | shell: bash 73 | run: | 74 | set -x 75 | python3 -m pip install -I -v . 76 | # Avoid importing the systemd module from the git repository 77 | cd / 78 | python3 -c 'from systemd import journal; print(journal.__version__)' 79 | pytest -v --pyargs systemd 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[co] 3 | /journald/*.so 4 | /TAGS 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | MANIFEST 19 | 20 | # Installer logs 21 | pip-log.txt 22 | 23 | # Unit test / coverage reports 24 | .coverage 25 | .tox 26 | .cache 27 | 28 | #Translations 29 | *.mo 30 | 31 | #Mr Developer 32 | .mr.developer.cfg 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include systemd/*.h 2 | include README.md 3 | include NEWS 4 | include LICENSE.txt 5 | include Makefile 6 | include pytest.ini 7 | graft docs 8 | exclude docs/__pycache__/* 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON = python 2 | SED = sed 3 | ETAGS = etags 4 | INCLUDE_DIR := $(shell pkg-config --variable=includedir libsystemd) 5 | INCLUDE_FLAGS := $(shell pkg-config --cflags libsystemd) 6 | VERSION := $(shell $(PYTHON) setup.py --version) 7 | TESTFLAGS = -v 8 | 9 | define buildscript 10 | import sys, sysconfig, setuptools 11 | sversion = int(setuptools.__version__.split(".")[0]) 12 | end = sys.implementation.cache_tag if sversion >= 61 else "{}.{}".format(*sys.version_info[:2]) 13 | print("build/lib.{}-{}".format(sysconfig.get_platform(), end)) 14 | endef 15 | 16 | builddir := $(shell $(PYTHON) -c '$(buildscript)') 17 | 18 | all: build 19 | 20 | .PHONY: update-constants 21 | update-constants: update-constants.py $(INCLUDE_DIR)/systemd/sd-messages.h 22 | $(PYTHON) $+ systemd/id128-defines.h | \ 23 | sort -u | \ 24 | tee systemd/id128-defines.h.tmp | \ 25 | $(SED) -n -r 's/,//g; s/#define (SD_MESSAGE_[A-Z0-9_]+)\s.*/add_id(m, "\1", \1) JOINER/p' | \ 26 | sort -u >systemd/id128-constants.h.tmp 27 | mv systemd/id128-defines.h{.tmp,} 28 | mv systemd/id128-constants.h{.tmp,} 29 | ($(SED) 9q docs/id128.rst.tmp 32 | mv docs/id128.rst{.tmp,} 33 | 34 | build: 35 | $(PYTHON) setup.py build_ext $(INCLUDE_FLAGS) 36 | $(PYTHON) setup.py build 37 | 38 | install: 39 | $(PYTHON) setup.py install --skip-build $(if $(DESTDIR),--root $(DESTDIR)) 40 | 41 | dist: 42 | $(PYTHON) setup.py sdist 43 | 44 | sign: dist/systemd-python-$(VERSION).tar.gz 45 | gpg --detach-sign -a dist/systemd-python-$(VERSION).tar.gz 46 | 47 | clean: 48 | rm -rf build systemd/*.so systemd/*.py[co] *.py[co] systemd/__pycache__ 49 | 50 | distclean: clean 51 | rm -rf dist MANIFEST 52 | 53 | SPHINXOPTS += -D version=$(VERSION) -D release=$(VERSION) 54 | sphinx-%: build 55 | cd build && \ 56 | PYTHONPATH=../$(builddir) $(PYTHON) -m sphinx -b $* $(SPHINXOPTS) ../docs $* 57 | @echo Output has been generated in build/$* 58 | 59 | doc: sphinx-html 60 | 61 | check: build 62 | (cd $(builddir) && $(PYTHON) -m pytest . ../../docs $(TESTFLAGS)) 63 | 64 | www_target = www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd/python-systemd 65 | doc-sync: 66 | rsync -rlv --delete --omit-dir-times build/html/ $(www_target)/ 67 | 68 | upload: dist/systemd-python-$(VERSION).tar.gz dist/systemd-python-$(VERSION).tar.gz.asc 69 | twine-3 upload $+ 70 | 71 | TAGS: $(shell git ls-files systemd/*.[ch]) 72 | $(ETAGS) $+ 73 | 74 | shell: 75 | # we change the directory because python insists on adding $CWD to path 76 | (cd $(builddir) && $(PYTHON)) 77 | 78 | .PHONY: build install dist sign upload clean distclean TAGS doc doc-sync shell 79 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Python wrappers for libsystemd API 2 | 3 | CHANGES WITH 235: 4 | 5 | * Adapt the rename of systemd-activate to systemd-socket-activate 6 | performed in systemd 230. 7 | 8 | * Support for sd_listen_fds_with_names added in systemd 227. 9 | 10 | * Support for sd_journal_get_cutoff_realtime_usec added in systemd 11 | 186. 12 | 13 | * Make the Reader PY_SSIZE_T_CLEAN for py3.10 compatibility. 14 | 15 | * id128: update for systemd-243 compatibility and other fixes. 16 | 17 | * C syntax modernization. A minimum of C99 is assumed. 18 | 19 | * Fix seek_realtime to work with timezone aware date on Python 3. 20 | 21 | * journal: add namespace support. 22 | 23 | * Fixes for memory leaks and documentation. 24 | 25 | * Support for Python 2 will be removed after this release. 26 | 27 | Contributions from: Alexander Olekhnovich, Andrew Stone, 28 | Architector #4, Chris Mullins, Dan Bungert, Dominik Prien, 29 | Federico Ceratto, Frantisek Sumsal, Glandos, Hendrikto, Khem 30 | Raj, Léonard Gérard, Marcel Waldvogel, Marco Paolini, Samuel 31 | BF, Tamaki Nishino, Tim Orling, Tomasz Meresiński, Zbigniew 32 | Jędrzejewski-Szmek, ytyt-yt 33 | 34 | CHANGES WITH 234: 35 | 36 | * Support for the new sd_is_socket_sockaddr added in systemd 233 37 | is added. 38 | 39 | * New id128 constants added in systemd 233 are included. 40 | 41 | * "Extra" fields for the log entry can be passed during 42 | LoggerAdapter initialization or for each message separately. 43 | Fields specified for the message have higher priority. 44 | 45 | * Small fixes and style tweaks all over. 46 | 47 | Contributions from: Park Jeongsoo, Jimmy Cao, Johannes 48 | Weberhofer, Mike Gilbert, Oleksii Shevchuk, Ville Skyttä, 49 | Wesley Bowman, Zbigniew Jędrzejewski-Szmek 50 | 51 | CHANGES WITH 233: 52 | 53 | * Tests are fixed and should pass on various old and new 54 | systems alike. 55 | 56 | * journal.stream() can be used without arguments and defaults 57 | to LOG_LEVEL. 58 | 59 | Contributions from Robert James Hernandez, 60 | Zbigniew Jędrzejewski-Szmek 61 | 62 | CHANGES WITH 232: 63 | 64 | * Wrappers for sd_journal_enumerate_unique, has_runtime_files, 65 | has_peristent_files. 66 | 67 | * sd_journal_open_directory_fd, sd_journal_open_files_fd can 68 | be used by passing file descriptors to the initializer as 69 | the path or files arguments. 70 | 71 | SD_JOURNAL_OS_ROOT flag is supported, and various flags may 72 | be passed to the constructor in combination with path or 73 | files arguments. All flags arguments are now passed through 74 | to the underlying libsystemd library functions, so which 75 | combinations are supported depends on that library. 76 | systemd 232 contains various fixes in this area. 77 | 78 | Contributions from: Benedit Morbach, Michael Biebl, 79 | Michael Herold, Mike Gilbert, Nir Soffer, Sebastian, 80 | Ville Skyttä, Zbigniew Jędrzejewski-Szmek 81 | 82 | CHANGES WITH 231: 83 | 84 | * Various build fixes and cleanups: documentation can be 85 | generated with 'make sphinx-html', pip install works out 86 | of the box. 87 | 88 | * Tests that cover most of the python code were added. As a 89 | result, a bug in sd_is_mq was fixed in systemd 227. 90 | 91 | * Functions sd_pid_notify and sd_pid_notify_with_fds are now 92 | wrapped as optional arguments to notify(), when compiled 93 | against a new-enough libsystemd. 94 | 95 | Contributions from: David Strauss, Evgeny Vereshchagin, 96 | Jacek Konieczny, Jeroen Dekkers, Zbigniew Jędrzejewski-Szmek 97 | 98 | CHANGES WITH 230: 99 | 100 | * python-systemd is again a separate project, after being part 101 | of systemd for many years. 102 | 103 | Contributions from: Dave Reisner, David Strauss, 104 | Evgeny Vereshchagin, Greg KH, Harald Hoyer, Jacek Konieczny, 105 | Jeroen Dekkers, Kay Sievers, Lennart Poettering, Lukas Nykryn, 106 | Marti Raudsepp, Richard Marko, Simon Farnsworth, 107 | Steven Hiscocks, Thomas Hindoe Paaboel Andersen, Ville Skyttä, 108 | Zbigniew Jędrzejewski-Szmek 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | python-systemd 2 | =============== 3 | 4 | Python module for native access to the systemd facilities. Functionality 5 | is separated into a number of modules: 6 | - `systemd.journal` supports sending of structured messages to the journal 7 | and reading journal files, 8 | - `systemd.daemon` wraps parts of `libsystemd` useful for writing daemons 9 | and socket activation, 10 | - `systemd.id128` provides functions for querying machine and boot identifiers 11 | and a lists of message identifiers provided by systemd, 12 | - `systemd.login` wraps parts of `libsystemd` used to query logged in users 13 | and available seats and machines. 14 | 15 | Installation 16 | ============ 17 | 18 | This module should be packaged for almost all Linux distributions. Use 19 | 20 | On Fedora: 21 | 22 | dnf install python3-systemd 23 | 24 | On Debian/Ubuntu/Mint: 25 | 26 | apt update 27 | apt install python3-systemd 28 | 29 | On openSUSE and SLE: 30 | 31 | zypper in python3-systemd 32 | 33 | On Arch: 34 | 35 | pacman -Sy python-systemd 36 | 37 | The project is also available on pypi as `systemd-python`: 38 | 39 | [![PyPI](https://img.shields.io/pypi/v/systemd-python.svg)](https://pypi.org/project/systemd-python/) 40 | 41 | To build from source 42 | -------------------- 43 | 44 | On CentOS, RHEL, and Fedora: 45 | 46 | dnf install git python3-pip gcc python3-devel systemd-devel 47 | pip3 install 'git+https://github.com/systemd/python-systemd.git#egg=systemd-python' 48 | 49 | On Debian or Ubuntu: 50 | 51 | apt install libsystemd-{journal,daemon,login,id128}-dev gcc python3-dev pkg-config 52 | 53 | Usage 54 | ===== 55 | 56 | Quick example: 57 | 58 | from systemd import journal 59 | journal.send('Hello world') 60 | journal.send('Hello, again, world', FIELD2='Greetings!', FIELD3='Guten tag') 61 | journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef') 62 | 63 | There is one required argument — the message, and additional fields 64 | can be specified as keyword arguments. Following the journald API, all 65 | names are uppercase. 66 | 67 | The journald sendv call can also be accessed directly: 68 | 69 | from systemd import journal 70 | journal.sendv('MESSAGE=Hello world') 71 | journal.sendv('MESSAGE=Hello, again, world', 'FIELD2=Greetings!', 72 | 'FIELD3=Guten tag') 73 | journal.sendv('MESSAGE=Binary message', b'BINARY=\xde\xad\xbe\xef') 74 | 75 | The two examples should give the same results in the log. 76 | 77 | Reading from the journal is often similar to using the `journalctl` utility. 78 | 79 | Show all entries since 20 minutes ago (`journalctl --since "20 minutes ago"`): 80 | 81 | from systemd import journal 82 | from datetime import datetime, timedelta 83 | j = journal.Reader() 84 | j.seek_realtime(datetime.now() - timedelta(minutes=20)) 85 | for entry in j: 86 | print(entry['MESSAGE']) 87 | 88 | Show entries between two timestamps (`journalctl --since "50 minutes ago" --until "10 minutes ago"`): 89 | 90 | from systemd import journal 91 | from datetime import datetime, timedelta 92 | j = journal.Reader() 93 | since = datetime.now() - timedelta(minutes=50) 94 | until = datetime.now() - timedelta(minutes=10) 95 | j.seek_realtime(since) 96 | for entry in j: 97 | if entry['__REALTIME_TIMESTAMP'] > until: 98 | break 99 | print(entry['MESSAGE']) 100 | 101 | Show explanations of log messages alongside entries (`journalctl -x`): 102 | 103 | from systemd import journal 104 | j = journal.Reader() 105 | for entry in j: 106 | print("MESSAGE: ", entry['MESSAGE']) 107 | try: 108 | print("CATALOG: ", j.get_catalog()) 109 | except: 110 | pass 111 | 112 | Show entries by a specific executable (`journalctl /usr/bin/vim`): 113 | 114 | from systemd import journal 115 | j = journal.Reader() 116 | j.add_match('_EXE=/usr/bin/vim') 117 | for entry in j: 118 | print(entry['MESSAGE']) 119 | 120 | - Note: matches can be added from many different fields, for example 121 | entries from a specific process ID can be matched with the `_PID` 122 | field, and entries from a specific unit (ie. `journalctl -u 123 | systemd-udevd.service`) can be matched with `_SYSTEMD_UNIT`. 124 | See all fields available at the 125 | [systemd.journal-fields docs](https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html). 126 | 127 | Show kernel ring buffer (`journalctl -k`): 128 | 129 | from systemd import journal 130 | j = journal.Reader() 131 | j.add_match('_TRANSPORT=kernel') 132 | for entry in j: 133 | print(entry['MESSAGE']) 134 | 135 | Read entries in reverse (`journalctl _EXE=/usr/bin/vim -r`): 136 | 137 | from systemd import journal 138 | class ReverseReader(journal.Reader): 139 | def __next__(self): 140 | ans = self.get_previous() 141 | if ans: 142 | return ans 143 | raise StopIteration() 144 | 145 | j = ReverseReader() 146 | j.add_match('_EXE=/usr/bin/vim') 147 | j.seek_tail() 148 | for entry in j: 149 | print(entry['MESSAGE']) 150 | 151 | 152 | Notes 153 | ----- 154 | 155 | * Unlike the native C version of journald's `sd_journal_send()`, 156 | printf-style substitution is not supported. Perform any substitution 157 | using Python's f-strings first (or `.format()` or the `%` operator). 158 | * A `ValueError` is raised if `sd_journald_sendv()` results in an 159 | error. This might happen if there are no arguments or one of them is 160 | invalid. 161 | 162 | A handler class for the Python logging framework is also provided: 163 | 164 | import logging 165 | from systemd import journal 166 | logger = logging.getLogger('custom_logger_name') 167 | logger.addHandler(journal.JournalHandler(SYSLOG_IDENTIFIER='custom_unit_name')) 168 | logger.warning("Some message: %s", 'detail') 169 | 170 | `libsystemd` version compatibility 171 | ---------------------------------- 172 | 173 | This module may be compiled against any version of `libsystemd`. At 174 | compilation time, any functionality that is not available in that 175 | version is disabled, and the resulting binary module will depend on 176 | symbols that were available at compilation time. This means that the 177 | resulting binary module is compatible with that or any later version 178 | of `libsystemd`. To obtain maximum possible functionality, this module 179 | must be compile against suitably recent libsystemd. 180 | 181 | Documentation 182 | ============= 183 | 184 | Online documentation can be found at [freedesktop.org](https://www.freedesktop.org/software/systemd/python-systemd/) 185 | 186 | To build it locally run: 187 | 188 | make sphinx-html 189 | 190 | Or use any other builder, see `man sphinx-build` for a list. The compiled docs will be e.g. in `docs/html`. 191 | 192 | Viewing Output 193 | ============== 194 | 195 | Quick way to view output with all fields as it comes in: 196 | 197 | sudo journalctl -f --output=json 198 | 199 | Test Builds (for Development) 200 | ============================= 201 | 202 | python setup.py build_ext -i 203 | python 204 | >>> from systemd import journal 205 | >>> journal.send("Test") 206 | 207 | [![Build Status](https://semaphoreci.com/api/v1/projects/42d43c62-f6e5-4fd5-a93a-2b165e6be575/530946/badge.svg)](https://semaphoreci.com/zbyszek/python-systemd) 208 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | !layout.html 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # python-systemd documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Feb 9 13:49:42 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['.'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'python-systemd' 44 | 45 | # The language for content autogenerated by Sphinx. Refer to documentation 46 | # for a list of supported languages. 47 | #language = None 48 | 49 | # There are two options for replacing |today|: either, you set today to some 50 | # non-false value, then it is used: 51 | #today = '' 52 | # Else, today_fmt is used as the format for a strftime call. 53 | #today_fmt = '%B %d, %Y' 54 | 55 | # List of patterns, relative to source directory, that match files and 56 | # directories to ignore when looking for source files. 57 | exclude_patterns = [] 58 | 59 | # The reST default role (used for this markup: `text`) to use for all documents. 60 | #default_role = None 61 | 62 | # If true, '()' will be appended to :func: etc. cross-reference text. 63 | #add_function_parentheses = True 64 | 65 | # If true, the current module name will be prepended to all description 66 | # unit titles (such as .. function::). 67 | #add_module_names = True 68 | 69 | # If true, sectionauthor and moduleauthor directives will be shown in the 70 | # output. They are ignored by default. 71 | #show_authors = False 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = 'sphinx' 75 | 76 | # A list of ignored prefixes for module index sorting. 77 | #modindex_common_prefix = [] 78 | 79 | 80 | # -- Options for HTML output --------------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | html_theme = 'default' 85 | 86 | # Theme options are theme-specific and customize the look and feel of a theme 87 | # further. For a list of options available for each theme, see the 88 | # documentation. 89 | #html_theme_options = {} 90 | 91 | # Add any paths that contain custom themes here, relative to this directory. 92 | #html_theme_path = [] 93 | 94 | # The name for this set of Sphinx documents. If None, it defaults to 95 | # " v documentation". 96 | #html_title = None 97 | 98 | # A shorter title for the navigation bar. Default is the same as html_title. 99 | #html_short_title = None 100 | 101 | # The name of an image file (relative to this directory) to place at the top 102 | # of the sidebar. 103 | #html_logo = None 104 | 105 | # The name of an image file (within the static path) to use as favicon of the 106 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 107 | # pixels large. 108 | #html_favicon = None 109 | 110 | # Add any paths that contain custom static files (such as style sheets) here, 111 | # relative to this directory. They are copied after the builtin static files, 112 | # so a file named "default.css" will overwrite the builtin "default.css". 113 | html_static_path = ['.'] 114 | 115 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 116 | # using the given strftime format. 117 | #html_last_updated_fmt = '%b %d, %Y' 118 | 119 | # If true, SmartyPants will be used to convert quotes and dashes to 120 | # typographically correct entities. 121 | #html_use_smartypants = True 122 | 123 | # Custom sidebar templates, maps document names to template names. 124 | #html_sidebars = {} 125 | 126 | # Additional templates that should be rendered to pages, maps page names to 127 | # template names. 128 | #html_additional_pages = {} 129 | 130 | # If false, no module index is generated. 131 | #html_domain_indices = True 132 | 133 | # If false, no index is generated. 134 | #html_use_index = True 135 | 136 | # If true, the index is split into individual pages for each letter. 137 | #html_split_index = False 138 | 139 | # If true, links to the reST sources are added to the pages. 140 | html_show_sourcelink = False 141 | 142 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 143 | #html_show_sphinx = True 144 | 145 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 146 | #html_show_copyright = True 147 | 148 | # If true, an OpenSearch description file will be output, and all pages will 149 | # contain a tag referring to it. The value of this option must be the 150 | # base URL from which the finished HTML is served. 151 | #html_use_opensearch = '' 152 | 153 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 154 | #html_file_suffix = None 155 | 156 | # Output file base name for HTML help builder. 157 | htmlhelp_basename = 'python-systemddoc' 158 | 159 | manpages_url = 'https://www.freedesktop.org/software/systemd/man/{page}.html' 160 | 161 | # -- Options for LaTeX output -------------------------------------------------- 162 | 163 | latex_elements = { 164 | # The paper size ('letterpaper' or 'a4paper'). 165 | #'papersize': 'letterpaper', 166 | 167 | # The font size ('10pt', '11pt' or '12pt'). 168 | #'pointsize': '10pt', 169 | 170 | # Additional stuff for the LaTeX preamble. 171 | #'preamble': '', 172 | } 173 | 174 | # Grouping the document tree into LaTeX files. List of tuples 175 | # (source start file, target name, title, author, documentclass [howto/manual]). 176 | latex_documents = [ 177 | ('index', 'python-systemd.tex', u'python-systemd Documentation', 178 | None, 'manual'), 179 | ] 180 | 181 | # The name of an image file (relative to this directory) to place at the top of 182 | # the title page. 183 | #latex_logo = None 184 | 185 | # For "manual" documents, if this is true, then toplevel headings are parts, 186 | # not chapters. 187 | #latex_use_parts = False 188 | 189 | # If true, show page references after internal links. 190 | #latex_show_pagerefs = False 191 | 192 | # If true, show URL addresses after external links. 193 | #latex_show_urls = False 194 | 195 | # Documents to append as an appendix to all manuals. 196 | #latex_appendices = [] 197 | 198 | # If false, no module index is generated. 199 | #latex_domain_indices = True 200 | 201 | 202 | # -- Options for manual page output -------------------------------------------- 203 | 204 | # One entry per manual page. List of tuples 205 | # (source start file, name, description, authors, manual section). 206 | man_pages = [ 207 | ('index', 'python-systemd', u'python-systemd Documentation', 208 | [], 1) 209 | ] 210 | 211 | # If true, show URL addresses after external links. 212 | #man_show_urls = False 213 | 214 | 215 | # -- Options for Texinfo output ------------------------------------------------ 216 | 217 | # Grouping the document tree into Texinfo files. List of tuples 218 | # (source start file, target name, title, author, 219 | # dir menu entry, description, category) 220 | texinfo_documents = [ 221 | ('index', 'python-systemd', u'python-systemd Documentation', 222 | u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks', 'python-systemd', 'One line description of project.', 223 | 'Miscellaneous'), 224 | ] 225 | 226 | # Documents to append as an appendix to all manuals. 227 | #texinfo_appendices = [] 228 | 229 | # If false, no module index is generated. 230 | #texinfo_domain_indices = True 231 | 232 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 233 | #texinfo_show_urls = 'footnote' 234 | 235 | 236 | # -- Options for Epub output --------------------------------------------------- 237 | 238 | # Bibliographic Dublin Core info. 239 | epub_title = u'python-systemd' 240 | epub_author = u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' 241 | epub_publisher = u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' 242 | epub_copyright = u'2013, David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' 243 | 244 | # The language of the text. It defaults to the language option 245 | # or en if the language is not set. 246 | #epub_language = '' 247 | 248 | # The scheme of the identifier. Typical schemes are ISBN or URL. 249 | #epub_scheme = '' 250 | 251 | # The unique identifier of the text. This can be a ISBN number 252 | # or the project homepage. 253 | #epub_identifier = '' 254 | 255 | # A unique identification for the text. 256 | #epub_uid = '' 257 | 258 | # A tuple containing the cover image and cover page html template filenames. 259 | #epub_cover = () 260 | 261 | # HTML files that should be inserted before the pages created by sphinx. 262 | # The format is a list of tuples containing the path and title. 263 | #epub_pre_files = [] 264 | 265 | # HTML files shat should be inserted after the pages created by sphinx. 266 | # The format is a list of tuples containing the path and title. 267 | #epub_post_files = [] 268 | 269 | # A list of files that should not be packed into the epub file. 270 | #epub_exclude_files = [] 271 | 272 | # The depth of the table of contents in toc.ncx. 273 | #epub_tocdepth = 3 274 | 275 | # Allow duplicate toc entries. 276 | #epub_tocdup = True 277 | 278 | # Example configuration for intersphinx: refer to the Python standard library. 279 | intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} 280 | -------------------------------------------------------------------------------- /docs/daemon.rst: -------------------------------------------------------------------------------- 1 | `systemd.daemon` module 2 | ======================= 3 | 4 | .. automodule:: systemd.daemon 5 | :members: 6 | :undoc-members: 7 | :inherited-members: 8 | 9 | .. autoattribute:: systemd.daemon.LISTEN_FDS_START 10 | 11 | .. autofunction:: _listen_fds 12 | .. autofunction:: _is_fifo 13 | .. autofunction:: _is_socket 14 | .. autofunction:: _is_socket_unix 15 | .. autofunction:: _is_socket_inet 16 | .. autofunction:: _is_mq 17 | .. autofunction:: notify 18 | .. autofunction:: booted 19 | -------------------------------------------------------------------------------- /docs/default.css: -------------------------------------------------------------------------------- 1 | @import url("basic.css"); 2 | 3 | /* -- page layout ----------------------------------------------------------- */ 4 | 5 | div.documentwrapper { 6 | float: left; 7 | width: 100%; 8 | } 9 | 10 | div.bodywrapper { 11 | margin: 0 0 0 230px; 12 | } 13 | 14 | div.body { 15 | background-color: #ffffff; 16 | color: #000000; 17 | padding: 0 20px 30px 20px; 18 | } 19 | 20 | div.footer { 21 | color: #ffffff; 22 | width: 100%; 23 | padding: 9px 0 9px 0; 24 | text-align: center; 25 | font-size: 75%; 26 | } 27 | 28 | div.footer a { 29 | color: #ffffff; 30 | text-decoration: underline; 31 | } 32 | 33 | div.related { 34 | background-color: #133f52; 35 | line-height: 30px; 36 | color: #ffffff; 37 | } 38 | 39 | div.related a { 40 | color: #ffffff; 41 | } 42 | 43 | div.sphinxsidebar { 44 | background-color: #dddddd; 45 | } 46 | 47 | div.sphinxsidebar p.topless { 48 | margin: 5px 10px 10px 10px; 49 | } 50 | 51 | div.sphinxsidebar ul { 52 | margin: 10px; 53 | padding: 0; 54 | } 55 | 56 | div.sphinxsidebar input { 57 | border: 1px solid #000000; 58 | font-family: sans-serif; 59 | font-size: 1em; 60 | } 61 | 62 | 63 | 64 | /* -- hyperlink styles ------------------------------------------------------ */ 65 | 66 | a { 67 | text-decoration: none; 68 | } 69 | 70 | a:hover { 71 | text-decoration: underline; 72 | } 73 | 74 | 75 | 76 | /* -- body styles ----------------------------------------------------------- */ 77 | 78 | div.body h1, 79 | div.body h2, 80 | div.body h3, 81 | div.body h4, 82 | div.body h5, 83 | div.body h6 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | background-color: #f2f2f2; 86 | font-weight: normal; 87 | color: #20435c; 88 | border-bottom: 1px solid #ccc; 89 | margin: 20px -20px 10px -20px; 90 | padding: 3px 0 3px 10px; 91 | } 92 | 93 | div.body h1 { margin-top: 0; font-size: 200%; } 94 | div.body h2 { font-size: 160%; } 95 | div.body h3 { font-size: 140%; } 96 | div.body h4 { font-size: 120%; } 97 | div.body h5 { font-size: 110%; } 98 | div.body h6 { font-size: 100%; } 99 | 100 | a.headerlink { 101 | color: #c60f0f; 102 | font-size: 0.8em; 103 | padding: 0 4px 0 4px; 104 | text-decoration: none; 105 | } 106 | 107 | a.headerlink:hover { 108 | background-color: #c60f0f; 109 | color: white; 110 | } 111 | 112 | div.body p, div.body dd, div.body li { 113 | text-align: justify; 114 | line-height: 130%; 115 | } 116 | 117 | div.admonition p.admonition-title + p { 118 | display: inline; 119 | } 120 | 121 | div.admonition p { 122 | margin-bottom: 5px; 123 | } 124 | 125 | div.admonition pre { 126 | margin-bottom: 5px; 127 | } 128 | 129 | div.admonition ul, div.admonition ol { 130 | margin-bottom: 5px; 131 | } 132 | 133 | div.note { 134 | background-color: #eee; 135 | border: 1px solid #ccc; 136 | } 137 | 138 | div.seealso { 139 | background-color: #ffc; 140 | border: 1px solid #ff6; 141 | } 142 | 143 | div.topic { 144 | background-color: #eee; 145 | } 146 | 147 | div.warning { 148 | background-color: #ffe4e4; 149 | border: 1px solid #f66; 150 | } 151 | 152 | p.admonition-title { 153 | display: inline; 154 | } 155 | 156 | p.admonition-title:after { 157 | content: ":"; 158 | } 159 | 160 | pre { 161 | padding: 5px; 162 | background-color: #eeffcc; 163 | color: #333333; 164 | line-height: 120%; 165 | border: 1px solid #ac9; 166 | border-left: none; 167 | border-right: none; 168 | } 169 | 170 | tt { 171 | background-color: #ecf0f3; 172 | padding: 0 1px 0 1px; 173 | font-size: 0.95em; 174 | } 175 | 176 | th { 177 | background-color: #ede; 178 | } 179 | 180 | .warning tt { 181 | background: #efc2c2; 182 | } 183 | 184 | .note tt { 185 | background: #d6d6d6; 186 | } 187 | 188 | .viewcode-back { 189 | font-family: sans-serif; 190 | } 191 | 192 | div.viewcode-block:target { 193 | background-color: #f4debf; 194 | border-top: 1px solid #ac9; 195 | border-bottom: 1px solid #ac9; 196 | } 197 | -------------------------------------------------------------------------------- /docs/id128.rst: -------------------------------------------------------------------------------- 1 | `systemd.id128` module 2 | ====================== 3 | 4 | .. automodule:: systemd.id128 5 | :members: 6 | :undoc-members: 7 | :inherited-members: 8 | 9 | .. autogenerated, do not edit! 10 | .. autoattribute:: systemd.id128.SD_MESSAGE_BACKTRACE 11 | .. autoattribute:: systemd.id128.SD_MESSAGE_BOOTCHART 12 | .. autoattribute:: systemd.id128.SD_MESSAGE_CONFIG_ERROR 13 | .. autoattribute:: systemd.id128.SD_MESSAGE_COREDUMP 14 | .. autoattribute:: systemd.id128.SD_MESSAGE_DEVICE_PATH_NOT_SUITABLE 15 | .. autoattribute:: systemd.id128.SD_MESSAGE_DNSSEC_DOWNGRADE 16 | .. autoattribute:: systemd.id128.SD_MESSAGE_DNSSEC_FAILURE 17 | .. autoattribute:: systemd.id128.SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED 18 | .. autoattribute:: systemd.id128.SD_MESSAGE_FACTORY_RESET 19 | .. autoattribute:: systemd.id128.SD_MESSAGE_FORWARD_SYSLOG_MISSED 20 | .. autoattribute:: systemd.id128.SD_MESSAGE_HIBERNATE_KEY_LONG_PRESS 21 | .. autoattribute:: systemd.id128.SD_MESSAGE_HIBERNATE_KEY 22 | .. autoattribute:: systemd.id128.SD_MESSAGE_INVALID_CONFIGURATION 23 | .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_DROPPED 24 | .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_MISSED 25 | .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_START 26 | .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_STOP 27 | .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_USAGE 28 | .. autoattribute:: systemd.id128.SD_MESSAGE_LID_CLOSED 29 | .. autoattribute:: systemd.id128.SD_MESSAGE_LID_OPENED 30 | .. autoattribute:: systemd.id128.SD_MESSAGE_MACHINE_START 31 | .. autoattribute:: systemd.id128.SD_MESSAGE_MACHINE_STOP 32 | .. autoattribute:: systemd.id128.SD_MESSAGE_MOUNT_POINT_PATH_NOT_SUITABLE 33 | .. autoattribute:: systemd.id128.SD_MESSAGE_NOBODY_USER_UNSUITABLE 34 | .. autoattribute:: systemd.id128.SD_MESSAGE_OVERMOUNTING 35 | .. autoattribute:: systemd.id128.SD_MESSAGE_POWER_KEY_LONG_PRESS 36 | .. autoattribute:: systemd.id128.SD_MESSAGE_POWER_KEY 37 | .. autoattribute:: systemd.id128.SD_MESSAGE_REBOOT_KEY_LONG_PRESS 38 | .. autoattribute:: systemd.id128.SD_MESSAGE_REBOOT_KEY 39 | .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_START 40 | .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_STOP 41 | .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_START 42 | .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_STOP 43 | .. autoattribute:: systemd.id128.SD_MESSAGE_SHUTDOWN 44 | .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_START 45 | .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_STOP 46 | .. autoattribute:: systemd.id128.SD_MESSAGE_SPAWN_FAILED 47 | .. autoattribute:: systemd.id128.SD_MESSAGE_STARTUP_FINISHED 48 | .. autoattribute:: systemd.id128.SD_MESSAGE_SUSPEND_KEY_LONG_PRESS 49 | .. autoattribute:: systemd.id128.SD_MESSAGE_SUSPEND_KEY 50 | .. autoattribute:: systemd.id128.SD_MESSAGE_SYSTEM_DOCKED 51 | .. autoattribute:: systemd.id128.SD_MESSAGE_SYSTEMD_UDEV_SETTLE_DEPRECATED 52 | .. autoattribute:: systemd.id128.SD_MESSAGE_SYSTEM_UNDOCKED 53 | .. autoattribute:: systemd.id128.SD_MESSAGE_TAINTED 54 | .. autoattribute:: systemd.id128.SD_MESSAGE_TIME_CHANGE 55 | .. autoattribute:: systemd.id128.SD_MESSAGE_TIME_SYNC 56 | .. autoattribute:: systemd.id128.SD_MESSAGE_TIMEZONE_CHANGE 57 | .. autoattribute:: systemd.id128.SD_MESSAGE_TRUNCATED_CORE 58 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_FAILED 59 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_FAILURE_RESULT 60 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_OOMD_KILL 61 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_OUT_OF_MEMORY 62 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_PROCESS_EXIT 63 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADED 64 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADING 65 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RESOURCES 66 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RESTART_SCHEDULED 67 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_SKIPPED 68 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTED 69 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTING 70 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPED 71 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPING 72 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_SUCCESS 73 | .. autoattribute:: systemd.id128.SD_MESSAGE_UNSAFE_USER_NAME 74 | .. autoattribute:: systemd.id128.SD_MESSAGE_USER_STARTUP_FINISHED 75 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. python-systemd documentation master file, created by 2 | sphinx-quickstart on Sat Feb 9 13:49:42 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. _index: 7 | 8 | python-systemd package 9 | ====================== 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | journal 17 | id128 18 | daemon 19 | login 20 | 21 | Links 22 | ===== 23 | 24 | * `systemd man pages `_ 25 | * `systemd directives `_ 26 | * git `repository `_ of this package 27 | * `systemd `_ home page 28 | * systemd git `repository `__ 29 | 30 | Indices and tables 31 | ================== 32 | 33 | * :ref:`genindex` 34 | * :ref:`modindex` 35 | * :ref:`search` 36 | -------------------------------------------------------------------------------- /docs/journal.rst: -------------------------------------------------------------------------------- 1 | `systemd.journal` module 2 | ======================== 3 | 4 | .. automodule:: systemd.journal 5 | :members: send, sendv, stream, stream_fd 6 | :undoc-members: 7 | 8 | `JournalHandler` class 9 | ---------------------- 10 | 11 | .. autoclass:: JournalHandler 12 | 13 | Accessing the Journal 14 | --------------------- 15 | 16 | .. autoclass:: _Reader 17 | :undoc-members: 18 | :inherited-members: 19 | 20 | .. autoclass:: Reader 21 | :undoc-members: 22 | :inherited-members: 23 | 24 | .. automethod:: __init__ 25 | 26 | .. autofunction:: _get_catalog 27 | .. autofunction:: get_catalog 28 | 29 | .. autoclass:: Monotonic 30 | 31 | .. autoattribute:: systemd.journal.DEFAULT_CONVERTERS 32 | 33 | Example: polling for journal events 34 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 35 | 36 | This example shows that journal events can be waited for (using 37 | e.g. `poll`). This makes it easy to integrate Reader in an external 38 | event loop: 39 | 40 | >>> import select 41 | >>> from systemd import journal 42 | >>> j = journal.Reader() 43 | >>> j.seek_tail() 44 | >>> journal.send('testing 1,2,3') # make sure we have something to read 45 | >>> j.add_match('MESSAGE=testing 1,2,3') 46 | >>> p = select.poll() 47 | >>> p.register(j, j.get_events()) 48 | >>> p.poll() # doctest: +SKIP 49 | [(3, 1)] 50 | >>> j.get_next() # doctest: +SKIP 51 | {'_AUDIT_LOGINUID': 1000, 52 | '_CAP_EFFECTIVE': '0', 53 | '_SELINUX_CONTEXT': 'unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023', 54 | '_GID': 1000, 55 | 'CODE_LINE': 1, 56 | '_HOSTNAME': '...', 57 | '_SYSTEMD_SESSION': 52, 58 | '_SYSTEMD_OWNER_UID': 1000, 59 | 'MESSAGE': 'testing 1,2,3', 60 | '__MONOTONIC_TIMESTAMP': 61 | journal.Monotonic(timestamp=datetime.timedelta(2, 76200, 811585), 62 | bootid=UUID('958b7e26-df4c-453a-a0f9-a8406cb508f2')), 63 | 'SYSLOG_IDENTIFIER': 'python3', 64 | '_UID': 1000, 65 | '_EXE': '/usr/bin/python3', 66 | '_PID': 7733, 67 | '_COMM': '...', 68 | 'CODE_FUNC': '', 69 | 'CODE_FILE': '', 70 | '_SOURCE_REALTIME_TIMESTAMP': 71 | datetime.datetime(2015, 9, 5, 13, 17, 4, 944355), 72 | '__CURSOR': 's=...', 73 | '_BOOT_ID': UUID('958b7e26-df4c-453a-a0f9-a8406cb508f2'), 74 | '_CMDLINE': '/usr/bin/python3 ...', 75 | '_MACHINE_ID': UUID('263bb31e-3e13-4062-9bdb-f1f4518999d2'), 76 | '_SYSTEMD_SLICE': 'user-1000.slice', 77 | '_AUDIT_SESSION': 52, 78 | '__REALTIME_TIMESTAMP': datetime.datetime(2015, 9, 5, 13, 17, 4, 945110), 79 | '_SYSTEMD_UNIT': 'session-52.scope', 80 | '_SYSTEMD_CGROUP': '/user.slice/user-1000.slice/session-52.scope', 81 | '_TRANSPORT': 'journal'} 82 | 83 | 84 | 85 | Journal access types 86 | ~~~~~~~~~~~~~~~~~~~~ 87 | 88 | .. autoattribute:: systemd.journal.LOCAL_ONLY 89 | .. autoattribute:: systemd.journal.RUNTIME_ONLY 90 | .. autoattribute:: systemd.journal.SYSTEM 91 | .. autoattribute:: systemd.journal.CURRENT_USER 92 | .. autoattribute:: systemd.journal.OS_ROOT 93 | 94 | Journal event types 95 | ~~~~~~~~~~~~~~~~~~~ 96 | 97 | .. autoattribute:: systemd.journal.NOP 98 | .. autoattribute:: systemd.journal.APPEND 99 | .. autoattribute:: systemd.journal.INVALIDATE 100 | -------------------------------------------------------------------------------- /docs/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block relbar1 %} 4 | python-systemd · 5 | man pages · 6 | directives 7 | 8 | python-systemd {{release}} 9 |
10 | {% endblock %} 11 | 12 | {# remove the lower relbar #} 13 | {% block relbar2 %} {% endblock %} 14 | 15 | {# remove the footer #} 16 | {% block footer %} {% endblock %} 17 | -------------------------------------------------------------------------------- /docs/login.rst: -------------------------------------------------------------------------------- 1 | `systemd.login` module 2 | ======================= 3 | 4 | .. automodule:: systemd.login 5 | :members: 6 | 7 | .. autoclass:: Monitor 8 | :undoc-members: 9 | :inherited-members: 10 | 11 | Example: polling for events 12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 13 | 14 | This example shows that session/uid/seat/machine events can be waited 15 | for (using e.g. `poll`). This makes it easy to integrate Monitor in an 16 | external event loop: 17 | 18 | >>> import select 19 | >>> from systemd import login 20 | >>> m = login.Monitor("machine") # doctest: +SKIP 21 | >>> p = select.poll() 22 | >>> p.register(m, m.get_events()) # doctest: +SKIP 23 | >>> login.machine_names() # doctest: +SKIP 24 | [] 25 | >>> p.poll() # doctest: +SKIP 26 | [(3, 1)] 27 | >>> login.machine_names() # doctest: +SKIP 28 | ['fedora-25'] 29 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --doctest-modules --doctest-glob=*.rst --ignore=setup.py 3 | norecursedirs = .git build 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | from setuptools import setup, Extension 3 | from subprocess import Popen, PIPE, check_output 4 | 5 | def call(*cmd): 6 | cmd = Popen(cmd, 7 | stdout=PIPE, stderr=PIPE, 8 | universal_newlines=True) 9 | if cmd.wait() == 0: 10 | return cmd.returncode, cmd.stdout.read() 11 | else: 12 | return cmd.returncode, cmd.stderr.read() 13 | 14 | def pkgconfig(package, **kw): 15 | pkg_version = package.replace('-', '_').upper() + '_VERSION' 16 | flag_map = {'-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries'} 17 | pkgconf = os.getenv('PKG_CONFIG', 'pkg-config') 18 | status, result = call(pkgconf, '--libs', '--cflags', package) 19 | if status != 0: 20 | return status, result 21 | for token in result.split(): 22 | kw.setdefault(flag_map.get(token[:2]), []).append(token[2:]) 23 | 24 | # allow version detection to be overridden using environment variables 25 | version = os.getenv(pkg_version) 26 | if not version: 27 | version = check_output([pkgconf, '--modversion', package], 28 | universal_newlines=True).strip() 29 | pair = (pkg_version, version) 30 | defines = kw.setdefault('define_macros', []) 31 | if pair not in defines: 32 | defines.append(pair) 33 | return status, kw 34 | 35 | def lib(*names, **kw): 36 | if '--version' in sys.argv: 37 | return {} 38 | results = [] 39 | for name in names: 40 | status, result = pkgconfig(name, **kw) 41 | if status == 0: 42 | return result 43 | results.append(result) 44 | sys.stderr.write('Cannot find ' + ' or '.join(names) + ':\n\n' 45 | + '\n'.join(results) + '\n') 46 | sys.exit(status) 47 | 48 | version = '235' 49 | defines = {'define_macros':[('PACKAGE_VERSION', '"{}"'.format(version))]} 50 | 51 | _journal = Extension('systemd/_journal', 52 | sources = ['systemd/_journal.c', 53 | 'systemd/pyutil.c'], 54 | extra_compile_args=['-std=c99', '-Werror=implicit-function-declaration'], 55 | **lib('libsystemd', 'libsystemd-journal', **defines)) 56 | _reader = Extension('systemd/_reader', 57 | sources = ['systemd/_reader.c', 58 | 'systemd/pyutil.c', 59 | 'systemd/strv.c'], 60 | extra_compile_args=['-std=c99', '-Werror=implicit-function-declaration'], 61 | **lib('libsystemd', 'libsystemd-journal', **defines)) 62 | _daemon = Extension('systemd/_daemon', 63 | sources = ['systemd/_daemon.c', 64 | 'systemd/pyutil.c', 65 | 'systemd/util.c'], 66 | extra_compile_args=['-std=c99', '-Werror=implicit-function-declaration'], 67 | **lib('libsystemd', 'libsystemd-daemon', **defines)) 68 | id128 = Extension('systemd/id128', 69 | sources = ['systemd/id128.c', 70 | 'systemd/pyutil.c'], 71 | extra_compile_args=['-std=c99', '-Werror=implicit-function-declaration'], 72 | **lib('libsystemd', 'libsystemd-id128', **defines)) 73 | login = Extension('systemd/login', 74 | sources = ['systemd/login.c', 75 | 'systemd/pyutil.c', 76 | 'systemd/strv.c'], 77 | extra_compile_args=['-std=c99', '-Werror=implicit-function-declaration'], 78 | **lib('libsystemd', 'libsystemd-login', **defines)) 79 | setup (name = 'systemd-python', 80 | version = version, 81 | description = 'Python interface for libsystemd', 82 | author_email = 'david@davidstrauss.net', 83 | maintainer = 'systemd developers', 84 | maintainer_email = 'systemd-devel@lists.freedesktop.org', 85 | url = 'https://github.com/systemd/python-systemd', 86 | license = 'LGPLv2+', 87 | classifiers = [ 88 | 'Programming Language :: Python :: 2', 89 | 'Programming Language :: Python :: 3', 90 | 'Topic :: Software Development :: Libraries :: Python Modules', 91 | 'Topic :: System :: Logging', 92 | 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', 93 | ], 94 | py_modules = ['systemd.journal', 'systemd.daemon', 95 | 'systemd.test.test_daemon', 96 | 'systemd.test.test_journal', 97 | 'systemd.test.test_login', 98 | 'systemd.test.test_id128'], 99 | ext_modules = [_journal, 100 | _reader, 101 | _daemon, 102 | id128, 103 | login]) 104 | -------------------------------------------------------------------------------- /systemd/.gitignore: -------------------------------------------------------------------------------- 1 | *.py[oc] 2 | *.so 3 | -------------------------------------------------------------------------------- /systemd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- Mode: python; indent-tabs-mode: nil -*- */ 2 | # 3 | # 4 | # Copyright 2012 David Strauss 5 | # 6 | # python-systemd is free software; you can redistribute it and/or modify it 7 | # under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation; either version 2.1 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # python-systemd is distributed in the hope that it will be useful, but 12 | # WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with python-systemd; If not, see . 18 | -------------------------------------------------------------------------------- /systemd/_daemon.c: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | /*** 4 | 5 | Copyright 2013-2016 Zbigniew Jędrzejewski-Szmek 6 | 7 | python-systemd is free software; you can redistribute it and/or modify it 8 | under the terms of the GNU Lesser General Public License as published by 9 | the Free Software Foundation; either version 2.1 of the License, or 10 | (at your option) any later version. 11 | 12 | python-systemd is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public License 18 | along with python-systemd; If not, see . 19 | ***/ 20 | 21 | #define PY_SSIZE_T_CLEAN 22 | #pragma GCC diagnostic push 23 | #pragma GCC diagnostic ignored "-Wredundant-decls" 24 | #include 25 | #pragma GCC diagnostic pop 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "systemd/sd-daemon.h" 32 | #include "pyutil.h" 33 | #include "macro.h" 34 | #include "util.h" 35 | 36 | #define HAVE_PID_NOTIFY (LIBSYSTEMD_VERSION >= 214) 37 | #define HAVE_PID_NOTIFY_WITH_FDS (LIBSYSTEMD_VERSION >= 219) 38 | #define HAVE_SD_LISTEN_FDS_WITH_NAMES (LIBSYSTEMD_VERSION >= 227) 39 | #define HAVE_IS_SOCKET_SOCKADDR (LIBSYSTEMD_VERSION >= 233) 40 | 41 | 42 | PyDoc_STRVAR(module__doc__, 43 | "Python interface to the libsystemd-daemon library.\n\n" 44 | "Provides _listen_fds*, notify, booted, and is_* functions\n" 45 | "which wrap sd_listen_fds*, sd_notify, sd_booted, sd_is_*;\n" 46 | "useful for socket activation and checking if the system is\n" 47 | "running under systemd." 48 | ); 49 | 50 | PyDoc_STRVAR(booted__doc__, 51 | "booted() -> bool\n\n" 52 | "Return True iff this system is running under systemd.\n" 53 | "Wraps sd_booted(3)." 54 | ); 55 | 56 | static PyObject* booted(PyObject *self, PyObject *args) { 57 | int r; 58 | assert(!args); 59 | 60 | r = sd_booted(); 61 | if (set_error(r, NULL, NULL) < 0) 62 | return NULL; 63 | 64 | return PyBool_FromLong(r); 65 | } 66 | 67 | static inline void PyMem_Free_intp(int **p) { 68 | PyMem_Free(*p); 69 | } 70 | 71 | PyDoc_STRVAR(notify__doc__, 72 | "notify(status, unset_environment=False, pid=0, fds=None) -> bool\n\n" 73 | "Send a message to the init system about a status change.\n" 74 | "Wraps sd_notify(3)."); 75 | 76 | static PyObject* notify(PyObject *self, PyObject *args, PyObject *keywds) { 77 | int r; 78 | const char* msg; 79 | int unset = false, n_fds; 80 | int _pid = 0; 81 | pid_t pid; 82 | PyObject *fds = NULL; 83 | _cleanup_(PyMem_Free_intp) int *arr = NULL; 84 | 85 | static const char* const kwlist[] = { 86 | "status", 87 | "unset_environment", 88 | "pid", 89 | "fds", 90 | NULL, 91 | }; 92 | if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|piO:notify", 93 | (char**) kwlist, &msg, &unset, &_pid, &fds)) 94 | return NULL; 95 | pid = _pid; 96 | if (pid < 0 || pid != _pid) { 97 | PyErr_SetString(PyExc_OverflowError, "Bad pid_t"); 98 | return NULL; 99 | } 100 | 101 | if (fds) { 102 | Py_ssize_t i, len; 103 | 104 | len = PySequence_Length(fds); 105 | if (len < 0) 106 | return NULL; 107 | 108 | arr = PyMem_NEW(int, len); 109 | if (!arr) 110 | return NULL; 111 | 112 | for (i = 0; i < len; i++) { 113 | _cleanup_Py_DECREF_ PyObject *item = PySequence_GetItem(fds, i); 114 | if (!item) 115 | return NULL; 116 | 117 | long value = PyLong_AsLong(item); 118 | if (PyErr_Occurred()) 119 | return NULL; 120 | 121 | arr[i] = value; 122 | if (arr[i] != value) { 123 | PyErr_SetString(PyExc_OverflowError, "Value to large for an integer"); 124 | return NULL; 125 | } 126 | } 127 | 128 | n_fds = len; 129 | } 130 | 131 | if (pid == 0 && !fds) 132 | r = sd_notify(unset, msg); 133 | else if (!fds) { 134 | #if HAVE_PID_NOTIFY 135 | r = sd_pid_notify(pid, unset, msg); 136 | #else 137 | set_error(-ENOSYS, NULL, "Compiled without support for sd_pid_notify"); 138 | return NULL; 139 | #endif 140 | } else { 141 | #if HAVE_PID_NOTIFY_WITH_FDS 142 | r = sd_pid_notify_with_fds(pid, unset, msg, arr, n_fds); 143 | #else 144 | set_error(-ENOSYS, NULL, "Compiled without support for sd_pid_notify_with_fds"); 145 | return NULL; 146 | #endif 147 | } 148 | 149 | if (set_error(r, NULL, NULL) < 0) 150 | return NULL; 151 | 152 | return PyBool_FromLong(r); 153 | } 154 | 155 | 156 | PyDoc_STRVAR(listen_fds__doc__, 157 | "_listen_fds(unset_environment=True) -> int\n\n" 158 | "Return the number of descriptors passed to this process by the init system\n" 159 | "as part of the socket-based activation logic.\n" 160 | "Wraps sd_listen_fds(3)." 161 | ); 162 | 163 | static PyObject* listen_fds(PyObject *self, PyObject *args, PyObject *keywds) { 164 | int r; 165 | int unset = true; 166 | 167 | static const char* const kwlist[] = {"unset_environment", NULL}; 168 | if (!PyArg_ParseTupleAndKeywords(args, keywds, "|p:_listen_fds", 169 | (char**) kwlist, &unset)) 170 | return NULL; 171 | 172 | r = sd_listen_fds(unset); 173 | if (set_error(r, NULL, NULL) < 0) 174 | return NULL; 175 | 176 | return long_FromLong(r); 177 | } 178 | 179 | PyDoc_STRVAR(listen_fds_with_names__doc__, 180 | "_listen_fds_with_names(unset_environment=True) -> (int, str...)\n\n" 181 | "Wraps sd_listen_fds_with_names(3).\n" 182 | #if HAVE_SD_LISTEN_FDS_WITH_NAMES 183 | "Return the number of descriptors passed to this process by the init system\n" 184 | "and their names as part of the socket-based activation logic.\n" 185 | #else 186 | "NOT SUPPORTED: compiled without support sd_listen_fds_with_names" 187 | #endif 188 | ); 189 | 190 | static void free_names(char **names) { 191 | if (names == NULL) 192 | return; 193 | for (char **n = names; *n != NULL; n++) 194 | free(*n); 195 | free(names); 196 | } 197 | static PyObject* listen_fds_with_names(PyObject *self, PyObject *args, PyObject *keywds) { 198 | int r; 199 | int unset = false; 200 | char **names = NULL; 201 | PyObject *tpl, *item; 202 | 203 | static const char* const kwlist[] = {"unset_environment", NULL}; 204 | if (!PyArg_ParseTupleAndKeywords(args, keywds, "|p:_listen_fds_with_names", 205 | (char**) kwlist, &unset)) 206 | return NULL; 207 | 208 | #if HAVE_SD_LISTEN_FDS_WITH_NAMES 209 | r = sd_listen_fds_with_names(unset, &names); 210 | if (set_error(r, NULL, NULL) < 0) 211 | return NULL; 212 | 213 | tpl = PyTuple_New(r+1); 214 | if (tpl == NULL) 215 | return NULL; 216 | 217 | item = long_FromLong(r); 218 | if (item == NULL) { 219 | Py_DECREF(tpl); 220 | return NULL; 221 | } 222 | if (PyTuple_SetItem(tpl, 0, item) < 0) { 223 | Py_DECREF(tpl); 224 | return NULL; 225 | } 226 | for (int i = 0; i < r && names[i] != NULL; i++) { 227 | item = unicode_FromString(names[i]); 228 | if (PyTuple_SetItem(tpl, 1+i, item) < 0) { 229 | Py_DECREF(tpl); 230 | free_names(names); 231 | return NULL; 232 | } 233 | } 234 | free_names(names); 235 | return tpl; 236 | #else /* !HAVE_SD_LISTEN_FDS_WITH_NAMES */ 237 | set_error(-ENOSYS, NULL, "Compiled without support for sd_listen_fds_with_names"); 238 | return NULL; 239 | #endif /* HAVE_SD_LISTEN_FDS_WITH_NAMES */ 240 | } 241 | 242 | PyDoc_STRVAR(is_fifo__doc__, 243 | "_is_fifo(fd, path) -> bool\n\n" 244 | "Returns True iff the descriptor refers to a FIFO or a pipe.\n" 245 | "Wraps sd_is_fifo(3)." 246 | ); 247 | 248 | 249 | static PyObject* is_fifo(PyObject *self, PyObject *args) { 250 | int r; 251 | int fd; 252 | const char *path = NULL; 253 | 254 | _cleanup_Py_DECREF_ PyObject *_path = NULL; 255 | if (!PyArg_ParseTuple(args, "i|O&:_is_fifo", 256 | &fd, Unicode_FSConverter, &_path)) 257 | return NULL; 258 | if (_path) 259 | path = PyBytes_AsString(_path); 260 | 261 | r = sd_is_fifo(fd, path); 262 | if (set_error(r, path, NULL) < 0) 263 | return NULL; 264 | 265 | return PyBool_FromLong(r); 266 | } 267 | 268 | 269 | PyDoc_STRVAR(is_mq__doc__, 270 | "_is_mq(fd, path) -> bool\n\n" 271 | "Returns True iff the descriptor refers to a POSIX message queue.\n" 272 | "Wraps sd_is_mq(3)." 273 | ); 274 | 275 | static PyObject* is_mq(PyObject *self, PyObject *args) { 276 | int r; 277 | int fd; 278 | const char *path = NULL; 279 | 280 | _cleanup_Py_DECREF_ PyObject *_path = NULL; 281 | if (!PyArg_ParseTuple(args, "i|O&:_is_mq", 282 | &fd, Unicode_FSConverter, &_path)) 283 | return NULL; 284 | if (_path) 285 | path = PyBytes_AsString(_path); 286 | 287 | r = sd_is_mq(fd, path); 288 | if (set_error(r, path, NULL) < 0) 289 | return NULL; 290 | 291 | return PyBool_FromLong(r); 292 | } 293 | 294 | 295 | 296 | PyDoc_STRVAR(is_socket__doc__, 297 | "_is_socket(fd, family=AF_UNSPEC, type=0, listening=-1) -> bool\n\n" 298 | "Returns True iff the descriptor refers to a socket.\n" 299 | "Wraps sd_is_socket(3).\n\n" 300 | "Constants for `family` are defined in the socket module." 301 | ); 302 | 303 | static PyObject* is_socket(PyObject *self, PyObject *args) { 304 | int r; 305 | int fd, family = AF_UNSPEC, type = 0, listening = -1; 306 | 307 | if (!PyArg_ParseTuple(args, "i|iii:_is_socket", 308 | &fd, &family, &type, &listening)) 309 | return NULL; 310 | 311 | r = sd_is_socket(fd, family, type, listening); 312 | if (set_error(r, NULL, NULL) < 0) 313 | return NULL; 314 | 315 | return PyBool_FromLong(r); 316 | } 317 | 318 | 319 | PyDoc_STRVAR(is_socket_inet__doc__, 320 | "_is_socket_inet(fd, family=AF_UNSPEC, type=0, listening=-1, port=0) -> bool\n\n" 321 | "Wraps sd_is_socket_inet(3).\n\n" 322 | "Constants for `family` are defined in the socket module." 323 | ); 324 | 325 | static PyObject* is_socket_inet(PyObject *self, PyObject *args) { 326 | int r; 327 | int fd, family = AF_UNSPEC, type = 0, listening = -1, port = 0; 328 | 329 | if (!PyArg_ParseTuple(args, "i|iiii:_is_socket_inet", 330 | &fd, &family, &type, &listening, &port)) 331 | return NULL; 332 | 333 | if (port < 0 || port > UINT16_MAX) { 334 | set_error(-EINVAL, NULL, "port must fit into uint16_t"); 335 | return NULL; 336 | } 337 | 338 | r = sd_is_socket_inet(fd, family, type, listening, (uint16_t) port); 339 | if (set_error(r, NULL, NULL) < 0) 340 | return NULL; 341 | 342 | return PyBool_FromLong(r); 343 | } 344 | 345 | PyDoc_STRVAR(is_socket_sockaddr__doc__, 346 | "_is_socket_sockaddr(fd, address, type=0, flowinfo=0, listening=-1) -> bool\n\n" 347 | "Wraps sd_is_socket_inet_sockaddr(3).\n" 348 | #if HAVE_IS_SOCKET_SOCKADDR 349 | "`address` is a systemd-style numerical IPv4 or IPv6 address as used in\n" 350 | "ListenStream=. A port may be included after a colon (\":\"). See\n" 351 | "systemd.socket(5) for details.\n\n" 352 | "Constants for `family` are defined in the socket module." 353 | #else 354 | "NOT SUPPORTED: compiled without support sd_socket_sockaddr" 355 | #endif 356 | ); 357 | 358 | static PyObject* is_socket_sockaddr(PyObject *self, PyObject *args) { 359 | int r; 360 | int fd, type = 0, flowinfo = 0, listening = -1; 361 | const char *address; 362 | union sockaddr_union addr = {}; 363 | unsigned addr_len; 364 | 365 | if (!PyArg_ParseTuple(args, "is|iii:_is_socket_sockaddr", 366 | &fd, 367 | &address, 368 | &type, 369 | &flowinfo, 370 | &listening)) 371 | return NULL; 372 | 373 | r = parse_sockaddr(address, &addr, &addr_len); 374 | if (r < 0) { 375 | set_error(r, NULL, "Cannot parse address"); 376 | return NULL; 377 | } 378 | 379 | if (flowinfo != 0) { 380 | if (addr.sa.sa_family != AF_INET6) { 381 | set_error(-EINVAL, NULL, "flowinfo is only applicable to IPv6 addresses"); 382 | return NULL; 383 | } 384 | 385 | addr.in6.sin6_flowinfo = flowinfo; 386 | } 387 | 388 | #if HAVE_IS_SOCKET_SOCKADDR 389 | r = sd_is_socket_sockaddr(fd, type, &addr.sa, addr_len, listening); 390 | if (set_error(r, NULL, NULL) < 0) 391 | return NULL; 392 | 393 | return PyBool_FromLong(r); 394 | #else 395 | set_error(-ENOSYS, NULL, "Compiled without support for sd_is_socket_sockaddr"); 396 | return NULL; 397 | #endif 398 | } 399 | 400 | PyDoc_STRVAR(is_socket_unix__doc__, 401 | "_is_socket_unix(fd, type, listening, path) -> bool\n\n" 402 | "Wraps sd_is_socket_unix(3)." 403 | ); 404 | 405 | static PyObject* is_socket_unix(PyObject *self, PyObject *args) { 406 | int r; 407 | int fd, type = 0, listening = -1; 408 | char* path = NULL; 409 | Py_ssize_t length = 0; 410 | 411 | _cleanup_Py_DECREF_ PyObject *_path = NULL; 412 | if (!PyArg_ParseTuple(args, "i|iiO&:_is_socket_unix", 413 | &fd, &type, &listening, Unicode_FSConverter, &_path)) 414 | return NULL; 415 | if (_path) { 416 | assert(PyBytes_Check(_path)); 417 | if (PyBytes_AsStringAndSize(_path, &path, &length)) 418 | return NULL; 419 | } 420 | 421 | r = sd_is_socket_unix(fd, type, listening, path, length); 422 | if (set_error(r, path, NULL) < 0) 423 | return NULL; 424 | 425 | return PyBool_FromLong(r); 426 | } 427 | 428 | 429 | static PyMethodDef methods[] = { 430 | { "booted", booted, METH_NOARGS, booted__doc__}, 431 | { "notify", (PyCFunction) notify, METH_VARARGS | METH_KEYWORDS, notify__doc__}, 432 | { "_listen_fds", (PyCFunction) listen_fds, METH_VARARGS | METH_KEYWORDS, listen_fds__doc__}, 433 | { "_listen_fds_with_names", (PyCFunction) listen_fds_with_names, 434 | METH_VARARGS | METH_KEYWORDS, listen_fds_with_names__doc__}, 435 | { "_is_fifo", is_fifo, METH_VARARGS, is_fifo__doc__}, 436 | { "_is_mq", is_mq, METH_VARARGS, is_mq__doc__}, 437 | { "_is_socket", is_socket, METH_VARARGS, is_socket__doc__}, 438 | { "_is_socket_inet", is_socket_inet, METH_VARARGS, is_socket_inet__doc__}, 439 | { "_is_socket_sockaddr", is_socket_sockaddr, METH_VARARGS, is_socket_sockaddr__doc__}, 440 | { "_is_socket_unix", is_socket_unix, METH_VARARGS, is_socket_unix__doc__}, 441 | {} /* Sentinel */ 442 | }; 443 | 444 | static struct PyModuleDef module = { 445 | PyModuleDef_HEAD_INIT, 446 | .m_name = "_daemon", /* name of module */ 447 | .m_doc = module__doc__, /* module documentation, may be NULL */ 448 | .m_size = 0, /* size of per-interpreter state of the module */ 449 | .m_methods = methods, 450 | }; 451 | 452 | DISABLE_WARNING_MISSING_PROTOTYPES; 453 | PyMODINIT_FUNC PyInit__daemon(void) { 454 | PyObject *m; 455 | 456 | m = PyModule_Create(&module); 457 | if (!m) 458 | return NULL; 459 | 460 | if (PyModule_AddIntConstant(m, "LISTEN_FDS_START", SD_LISTEN_FDS_START) || 461 | PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { 462 | Py_DECREF(m); 463 | return NULL; 464 | } 465 | 466 | return m; 467 | } 468 | REENABLE_WARNING; 469 | -------------------------------------------------------------------------------- /systemd/_journal.c: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | /*** 4 | 5 | Copyright 2012 David Strauss 6 | 7 | python-systemd is free software; you can redistribute it and/or modify it 8 | under the terms of the GNU Lesser General Public License as published by 9 | the Free Software Foundation; either version 2.1 of the License, or 10 | (at your option) any later version. 11 | 12 | python-systemd is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public License 18 | along with python-systemd; If not, see . 19 | ***/ 20 | 21 | #include 22 | 23 | #include 24 | 25 | #define SD_JOURNAL_SUPPRESS_LOCATION 26 | #include "systemd/sd-journal.h" 27 | 28 | #include "macro.h" 29 | 30 | PyDoc_STRVAR(journal_sendv__doc__, 31 | "sendv('FIELD=value', 'FIELD=value', ...) -> None\n\n" 32 | "Send an entry to the journal." 33 | ); 34 | 35 | static PyObject *journal_sendv(PyObject *self, PyObject *args) { 36 | struct iovec *iov = NULL; 37 | int argc; 38 | int i, r; 39 | PyObject *ret = NULL; 40 | PyObject **encoded; 41 | 42 | /* Allocate an array for the argument strings */ 43 | argc = PyTuple_Size(args); 44 | encoded = alloca0(argc * sizeof(PyObject*)); 45 | 46 | /* Allocate sufficient iovector space for the arguments. */ 47 | iov = alloca(argc * sizeof(struct iovec)); 48 | 49 | /* Iterate through the Python arguments and fill the iovector. */ 50 | for (i = 0; i < argc; ++i) { 51 | PyObject *item = PyTuple_GetItem(args, i); 52 | char *stritem; 53 | Py_ssize_t length; 54 | 55 | if (PyUnicode_Check(item)) { 56 | encoded[i] = PyUnicode_AsEncodedString(item, "utf-8", "strict"); 57 | if (!encoded[i]) 58 | goto out; 59 | item = encoded[i]; 60 | } 61 | if (PyBytes_AsStringAndSize(item, &stritem, &length)) 62 | goto out; 63 | 64 | iov[i].iov_base = stritem; 65 | iov[i].iov_len = length; 66 | } 67 | 68 | /* Send the iovector to the journal. */ 69 | r = sd_journal_sendv(iov, argc); 70 | if (r < 0) { 71 | errno = -r; 72 | PyErr_SetFromErrno(PyExc_IOError); 73 | goto out; 74 | } 75 | 76 | /* End with success. */ 77 | Py_INCREF(Py_None); 78 | ret = Py_None; 79 | 80 | out: 81 | for (i = 0; i < argc; ++i) 82 | Py_XDECREF(encoded[i]); 83 | 84 | return ret; 85 | } 86 | 87 | PyDoc_STRVAR(journal_stream_fd__doc__, 88 | "stream_fd(identifier, priority, level_prefix) -> fd\n\n" 89 | "Open a stream to journal by calling sd_journal_stream_fd(3)." 90 | ); 91 | 92 | static PyObject* journal_stream_fd(PyObject *self, PyObject *args) { 93 | const char* identifier; 94 | int priority, level_prefix; 95 | int fd; 96 | 97 | if (!PyArg_ParseTuple(args, "sii:stream_fd", 98 | &identifier, &priority, &level_prefix)) 99 | return NULL; 100 | 101 | fd = sd_journal_stream_fd(identifier, priority, level_prefix); 102 | if (fd < 0) { 103 | errno = -fd; 104 | return PyErr_SetFromErrno(PyExc_IOError); 105 | } 106 | 107 | return PyLong_FromLong(fd); 108 | } 109 | 110 | static PyMethodDef methods[] = { 111 | { "sendv", journal_sendv, METH_VARARGS, journal_sendv__doc__ }, 112 | { "stream_fd", journal_stream_fd, METH_VARARGS, journal_stream_fd__doc__ }, 113 | {} /* Sentinel */ 114 | }; 115 | 116 | static struct PyModuleDef module = { 117 | PyModuleDef_HEAD_INIT, 118 | .m_name = "_journal", /* name of module */ 119 | .m_size = -1, /* size of per-interpreter state of the module */ 120 | .m_methods = methods, 121 | }; 122 | 123 | DISABLE_WARNING_MISSING_PROTOTYPES; 124 | PyMODINIT_FUNC PyInit__journal(void) { 125 | PyObject *m; 126 | 127 | m = PyModule_Create(&module); 128 | if (!m) 129 | return NULL; 130 | 131 | if (PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { 132 | Py_DECREF(m); 133 | return NULL; 134 | } 135 | 136 | return m; 137 | } 138 | REENABLE_WARNING; 139 | -------------------------------------------------------------------------------- /systemd/daemon.py: -------------------------------------------------------------------------------- 1 | from socket import AF_UNSPEC as _AF_UNSPEC 2 | 3 | from ._daemon import (__version__, 4 | booted, 5 | notify, 6 | _listen_fds, 7 | _listen_fds_with_names, 8 | _is_fifo, 9 | _is_socket, 10 | _is_socket_inet, 11 | _is_socket_sockaddr, 12 | _is_socket_unix, 13 | _is_mq, 14 | LISTEN_FDS_START) 15 | 16 | def _convert_fileobj(fileobj): 17 | try: 18 | return fileobj.fileno() 19 | except AttributeError: 20 | return fileobj 21 | 22 | def is_fifo(fileobj, path=None): 23 | fd = _convert_fileobj(fileobj) 24 | return _is_fifo(fd, path) 25 | 26 | def is_socket(fileobj, family=_AF_UNSPEC, type=0, listening=-1): 27 | fd = _convert_fileobj(fileobj) 28 | return _is_socket(fd, family, type, listening) 29 | 30 | def is_socket_inet(fileobj, family=_AF_UNSPEC, type=0, listening=-1, port=0): 31 | fd = _convert_fileobj(fileobj) 32 | return _is_socket_inet(fd, family, type, listening, port) 33 | 34 | def is_socket_sockaddr(fileobj, address, type=0, flowinfo=0, listening=-1): 35 | """Check socket type, address and/or port, flowinfo, listening state. 36 | 37 | Wraps sd_is_socket_inet_sockaddr(3). 38 | 39 | `address` is a systemd-style numerical IPv4 or IPv6 address as used in 40 | ListenStream=. A port may be included after a colon (":"). 41 | See systemd.socket(5) for details. 42 | 43 | Constants for `family` are defined in the socket module. 44 | """ 45 | fd = _convert_fileobj(fileobj) 46 | return _is_socket_sockaddr(fd, address, type, flowinfo, listening) 47 | 48 | def is_socket_unix(fileobj, type=0, listening=-1, path=None): 49 | fd = _convert_fileobj(fileobj) 50 | return _is_socket_unix(fd, type, listening, path) 51 | 52 | def is_mq(fileobj, path=None): 53 | fd = _convert_fileobj(fileobj) 54 | return _is_mq(fd, path) 55 | 56 | def listen_fds(unset_environment=True): 57 | """Return a list of socket activated descriptors 58 | 59 | Example:: 60 | 61 | (in primary window) 62 | $ systemd-socket-activate -l 2000 python3 -c \\ 63 | 'from systemd.daemon import listen_fds; print(listen_fds())' 64 | (in another window) 65 | $ telnet localhost 2000 66 | (in primary window) 67 | ... 68 | Execing python3 (...) 69 | [3] 70 | """ 71 | num = _listen_fds(unset_environment) 72 | return list(range(LISTEN_FDS_START, LISTEN_FDS_START + num)) 73 | 74 | def listen_fds_with_names(unset_environment=True): 75 | """Return a dictionary of socket activated descriptors as {fd: name} 76 | 77 | Example:: 78 | 79 | (in primary window) 80 | $ systemd-socket-activate -l 2000 -l 4000 --fdname=2K:4K python3 -c \\ 81 | 'from systemd.daemon import listen_fds_with_names; print(listen_fds_with_names())' 82 | (in another window) 83 | $ telnet localhost 2000 84 | (in primary window) 85 | ... 86 | Execing python3 (...) 87 | [3] 88 | """ 89 | composite = _listen_fds_with_names(unset_environment) 90 | retval = {} 91 | for i in range(0, composite[0]): 92 | retval[i+LISTEN_FDS_START] = composite[1+i] 93 | return retval 94 | -------------------------------------------------------------------------------- /systemd/id128-constants.h: -------------------------------------------------------------------------------- 1 | add_id(m, "SD_MESSAGE_BACKTRACE", SD_MESSAGE_BACKTRACE) JOINER 2 | add_id(m, "SD_MESSAGE_BOOTCHART", SD_MESSAGE_BOOTCHART) JOINER 3 | add_id(m, "SD_MESSAGE_CONFIG_ERROR", SD_MESSAGE_CONFIG_ERROR) JOINER 4 | add_id(m, "SD_MESSAGE_COREDUMP", SD_MESSAGE_COREDUMP) JOINER 5 | add_id(m, "SD_MESSAGE_DEVICE_PATH_NOT_SUITABLE", SD_MESSAGE_DEVICE_PATH_NOT_SUITABLE) JOINER 6 | add_id(m, "SD_MESSAGE_DNSSEC_DOWNGRADE", SD_MESSAGE_DNSSEC_DOWNGRADE) JOINER 7 | add_id(m, "SD_MESSAGE_DNSSEC_FAILURE", SD_MESSAGE_DNSSEC_FAILURE) JOINER 8 | add_id(m, "SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED", SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED) JOINER 9 | add_id(m, "SD_MESSAGE_FACTORY_RESET", SD_MESSAGE_FACTORY_RESET) JOINER 10 | add_id(m, "SD_MESSAGE_FORWARD_SYSLOG_MISSED", SD_MESSAGE_FORWARD_SYSLOG_MISSED) JOINER 11 | add_id(m, "SD_MESSAGE_HIBERNATE_KEY_LONG_PRESS", SD_MESSAGE_HIBERNATE_KEY_LONG_PRESS) JOINER 12 | add_id(m, "SD_MESSAGE_HIBERNATE_KEY", SD_MESSAGE_HIBERNATE_KEY) JOINER 13 | add_id(m, "SD_MESSAGE_INVALID_CONFIGURATION", SD_MESSAGE_INVALID_CONFIGURATION) JOINER 14 | add_id(m, "SD_MESSAGE_JOURNAL_DROPPED", SD_MESSAGE_JOURNAL_DROPPED) JOINER 15 | add_id(m, "SD_MESSAGE_JOURNAL_MISSED", SD_MESSAGE_JOURNAL_MISSED) JOINER 16 | add_id(m, "SD_MESSAGE_JOURNAL_START", SD_MESSAGE_JOURNAL_START) JOINER 17 | add_id(m, "SD_MESSAGE_JOURNAL_STOP", SD_MESSAGE_JOURNAL_STOP) JOINER 18 | add_id(m, "SD_MESSAGE_JOURNAL_USAGE", SD_MESSAGE_JOURNAL_USAGE) JOINER 19 | add_id(m, "SD_MESSAGE_LID_CLOSED", SD_MESSAGE_LID_CLOSED) JOINER 20 | add_id(m, "SD_MESSAGE_LID_OPENED", SD_MESSAGE_LID_OPENED) JOINER 21 | add_id(m, "SD_MESSAGE_MACHINE_START", SD_MESSAGE_MACHINE_START) JOINER 22 | add_id(m, "SD_MESSAGE_MACHINE_STOP", SD_MESSAGE_MACHINE_STOP) JOINER 23 | add_id(m, "SD_MESSAGE_MOUNT_POINT_PATH_NOT_SUITABLE", SD_MESSAGE_MOUNT_POINT_PATH_NOT_SUITABLE) JOINER 24 | add_id(m, "SD_MESSAGE_NOBODY_USER_UNSUITABLE", SD_MESSAGE_NOBODY_USER_UNSUITABLE) JOINER 25 | add_id(m, "SD_MESSAGE_OVERMOUNTING", SD_MESSAGE_OVERMOUNTING) JOINER 26 | add_id(m, "SD_MESSAGE_POWER_KEY_LONG_PRESS", SD_MESSAGE_POWER_KEY_LONG_PRESS) JOINER 27 | add_id(m, "SD_MESSAGE_POWER_KEY", SD_MESSAGE_POWER_KEY) JOINER 28 | add_id(m, "SD_MESSAGE_REBOOT_KEY_LONG_PRESS", SD_MESSAGE_REBOOT_KEY_LONG_PRESS) JOINER 29 | add_id(m, "SD_MESSAGE_REBOOT_KEY", SD_MESSAGE_REBOOT_KEY) JOINER 30 | add_id(m, "SD_MESSAGE_SEAT_START", SD_MESSAGE_SEAT_START) JOINER 31 | add_id(m, "SD_MESSAGE_SEAT_STOP", SD_MESSAGE_SEAT_STOP) JOINER 32 | add_id(m, "SD_MESSAGE_SESSION_START", SD_MESSAGE_SESSION_START) JOINER 33 | add_id(m, "SD_MESSAGE_SESSION_STOP", SD_MESSAGE_SESSION_STOP) JOINER 34 | add_id(m, "SD_MESSAGE_SHUTDOWN", SD_MESSAGE_SHUTDOWN) JOINER 35 | add_id(m, "SD_MESSAGE_SLEEP_START", SD_MESSAGE_SLEEP_START) JOINER 36 | add_id(m, "SD_MESSAGE_SLEEP_STOP", SD_MESSAGE_SLEEP_STOP) JOINER 37 | add_id(m, "SD_MESSAGE_SPAWN_FAILED", SD_MESSAGE_SPAWN_FAILED) JOINER 38 | add_id(m, "SD_MESSAGE_STARTUP_FINISHED", SD_MESSAGE_STARTUP_FINISHED) JOINER 39 | add_id(m, "SD_MESSAGE_SUSPEND_KEY_LONG_PRESS", SD_MESSAGE_SUSPEND_KEY_LONG_PRESS) JOINER 40 | add_id(m, "SD_MESSAGE_SUSPEND_KEY", SD_MESSAGE_SUSPEND_KEY) JOINER 41 | add_id(m, "SD_MESSAGE_SYSTEM_DOCKED", SD_MESSAGE_SYSTEM_DOCKED) JOINER 42 | add_id(m, "SD_MESSAGE_SYSTEMD_UDEV_SETTLE_DEPRECATED", SD_MESSAGE_SYSTEMD_UDEV_SETTLE_DEPRECATED) JOINER 43 | add_id(m, "SD_MESSAGE_SYSTEM_UNDOCKED", SD_MESSAGE_SYSTEM_UNDOCKED) JOINER 44 | add_id(m, "SD_MESSAGE_TAINTED", SD_MESSAGE_TAINTED) JOINER 45 | add_id(m, "SD_MESSAGE_TIME_CHANGE", SD_MESSAGE_TIME_CHANGE) JOINER 46 | add_id(m, "SD_MESSAGE_TIME_SYNC", SD_MESSAGE_TIME_SYNC) JOINER 47 | add_id(m, "SD_MESSAGE_TIMEZONE_CHANGE", SD_MESSAGE_TIMEZONE_CHANGE) JOINER 48 | add_id(m, "SD_MESSAGE_TRUNCATED_CORE", SD_MESSAGE_TRUNCATED_CORE) JOINER 49 | add_id(m, "SD_MESSAGE_UNIT_FAILED", SD_MESSAGE_UNIT_FAILED) JOINER 50 | add_id(m, "SD_MESSAGE_UNIT_FAILURE_RESULT", SD_MESSAGE_UNIT_FAILURE_RESULT) JOINER 51 | add_id(m, "SD_MESSAGE_UNIT_OOMD_KILL", SD_MESSAGE_UNIT_OOMD_KILL) JOINER 52 | add_id(m, "SD_MESSAGE_UNIT_OUT_OF_MEMORY", SD_MESSAGE_UNIT_OUT_OF_MEMORY) JOINER 53 | add_id(m, "SD_MESSAGE_UNIT_PROCESS_EXIT", SD_MESSAGE_UNIT_PROCESS_EXIT) JOINER 54 | add_id(m, "SD_MESSAGE_UNIT_RELOADED", SD_MESSAGE_UNIT_RELOADED) JOINER 55 | add_id(m, "SD_MESSAGE_UNIT_RELOADING", SD_MESSAGE_UNIT_RELOADING) JOINER 56 | add_id(m, "SD_MESSAGE_UNIT_RESOURCES", SD_MESSAGE_UNIT_RESOURCES) JOINER 57 | add_id(m, "SD_MESSAGE_UNIT_RESTART_SCHEDULED", SD_MESSAGE_UNIT_RESTART_SCHEDULED) JOINER 58 | add_id(m, "SD_MESSAGE_UNIT_SKIPPED", SD_MESSAGE_UNIT_SKIPPED) JOINER 59 | add_id(m, "SD_MESSAGE_UNIT_STARTED", SD_MESSAGE_UNIT_STARTED) JOINER 60 | add_id(m, "SD_MESSAGE_UNIT_STARTING", SD_MESSAGE_UNIT_STARTING) JOINER 61 | add_id(m, "SD_MESSAGE_UNIT_STOPPED", SD_MESSAGE_UNIT_STOPPED) JOINER 62 | add_id(m, "SD_MESSAGE_UNIT_STOPPING", SD_MESSAGE_UNIT_STOPPING) JOINER 63 | add_id(m, "SD_MESSAGE_UNIT_SUCCESS", SD_MESSAGE_UNIT_SUCCESS) JOINER 64 | add_id(m, "SD_MESSAGE_UNSAFE_USER_NAME", SD_MESSAGE_UNSAFE_USER_NAME) JOINER 65 | add_id(m, "SD_MESSAGE_USER_STARTUP_FINISHED", SD_MESSAGE_USER_STARTUP_FINISHED) JOINER 66 | -------------------------------------------------------------------------------- /systemd/id128-defines.h: -------------------------------------------------------------------------------- 1 | #define SD_MESSAGE_BACKTRACE SD_ID128_MAKE(1f,4e,0a,44,a8,86,49,93,9a,ae,a3,4f,c6,da,8c,95) 2 | #define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18) 3 | #define SD_MESSAGE_CONFIG_ERROR SD_ID128_MAKE(c7,72,d2,4e,9a,88,4c,be,b9,ea,12,62,5c,30,6c,01) 4 | #define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) 5 | #define SD_MESSAGE_DEVICE_PATH_NOT_SUITABLE SD_ID128_MAKE(01,01,90,13,8f,49,4e,29,a0,ef,66,69,74,95,31,aa) 6 | #define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) 7 | #define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) 8 | #define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) 9 | #define SD_MESSAGE_FACTORY_RESET SD_ID128_MAKE(c1,4a,af,76,ec,28,4a,5f,a1,f1,05,f8,8d,fb,06,1c) 10 | #define SD_MESSAGE_FORWARD_SYSLOG_MISSED SD_ID128_MAKE(00,27,22,9c,a0,64,41,81,a7,6c,4e,92,45,8a,fa,2e) 11 | #define SD_MESSAGE_HIBERNATE_KEY_LONG_PRESS SD_ID128_MAKE(16,78,36,df,6f,7f,42,8e,98,14,72,27,b2,dc,89,45) 12 | #define SD_MESSAGE_HIBERNATE_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,73) 13 | #define SD_MESSAGE_INVALID_CONFIGURATION SD_ID128_MAKE(c7,72,d2,4e,9a,88,4c,be,b9,ea,12,62,5c,30,6c,01) 14 | #define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e) 15 | #define SD_MESSAGE_JOURNAL_MISSED SD_ID128_MAKE(e9,bf,28,e6,e8,34,48,1b,b6,f4,8f,54,8a,d1,36,06) 16 | #define SD_MESSAGE_JOURNAL_START SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b) 17 | #define SD_MESSAGE_JOURNAL_STOP SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b) 18 | #define SD_MESSAGE_JOURNAL_USAGE SD_ID128_MAKE(ec,38,7f,57,7b,84,4b,8f,a9,48,f3,3c,ad,9a,75,e6) 19 | #define SD_MESSAGE_LID_CLOSED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,70) 20 | #define SD_MESSAGE_LID_OPENED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,6f) 21 | #define SD_MESSAGE_MACHINE_START SD_ID128_MAKE(24,d8,d4,45,25,73,40,24,96,06,83,81,a6,31,2d,f2) 22 | #define SD_MESSAGE_MACHINE_STOP SD_ID128_MAKE(58,43,2b,d3,ba,ce,47,7c,b5,14,b5,63,81,b8,a7,58) 23 | #define SD_MESSAGE_MOUNT_POINT_PATH_NOT_SUITABLE SD_ID128_MAKE(1b,3b,b9,40,37,f0,4b,bf,81,02,8e,13,5a,12,d2,93) 24 | #define SD_MESSAGE_NOBODY_USER_UNSUITABLE SD_ID128_MAKE(b4,80,32,5f,9c,39,4a,7b,80,2c,23,1e,51,a2,75,2c) 25 | #define SD_MESSAGE_OVERMOUNTING SD_ID128_MAKE(1d,ee,03,69,c7,fc,47,36,b7,09,9b,38,ec,b4,6e,e7) 26 | #define SD_MESSAGE_POWER_KEY_LONG_PRESS SD_ID128_MAKE(3e,01,17,10,1e,b2,43,c1,b9,a5,0d,b3,49,4a,b1,0b) 27 | #define SD_MESSAGE_POWER_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,71) 28 | #define SD_MESSAGE_REBOOT_KEY_LONG_PRESS SD_ID128_MAKE(f1,c5,9a,58,c9,d9,43,66,89,65,c3,37,ca,ec,59,75) 29 | #define SD_MESSAGE_REBOOT_KEY SD_ID128_MAKE(9f,a9,d2,c0,12,13,4e,c3,85,45,1f,fe,31,6f,97,d0) 30 | #define SD_MESSAGE_SEAT_START SD_ID128_MAKE(fc,be,fc,5d,a2,3d,42,80,93,f9,7c,82,a9,29,0f,7b) 31 | #define SD_MESSAGE_SEAT_STOP SD_ID128_MAKE(e7,85,2b,fe,46,78,4e,d0,ac,cd,e0,4b,c8,64,c2,d5) 32 | #define SD_MESSAGE_SESSION_START SD_ID128_MAKE(8d,45,62,0c,1a,43,48,db,b1,74,10,da,57,c6,0c,66) 33 | #define SD_MESSAGE_SESSION_STOP SD_ID128_MAKE(33,54,93,94,24,b4,45,6d,98,02,ca,83,33,ed,42,4a) 34 | #define SD_MESSAGE_SHUTDOWN SD_ID128_MAKE(98,26,88,66,d1,d5,4a,49,9c,4e,98,92,1d,93,bc,40) 35 | #define SD_MESSAGE_SLEEP_START SD_ID128_MAKE(6b,bd,95,ee,97,79,41,e4,97,c4,8b,e2,7c,25,41,28) 36 | #define SD_MESSAGE_SLEEP_STOP SD_ID128_MAKE(88,11,e6,df,2a,8e,40,f5,8a,94,ce,a2,6f,8e,bf,14) 37 | #define SD_MESSAGE_SPAWN_FAILED SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) 38 | #define SD_MESSAGE_STARTUP_FINISHED SD_ID128_MAKE(b0,7a,24,9c,d0,24,41,4a,82,dd,00,cd,18,13,78,ff) 39 | #define SD_MESSAGE_SUSPEND_KEY_LONG_PRESS SD_ID128_MAKE(bf,da,f6,d3,12,ab,40,07,bc,1f,e4,0a,15,df,78,e8) 40 | #define SD_MESSAGE_SUSPEND_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,72) 41 | #define SD_MESSAGE_SYSTEM_DOCKED SD_ID128_MAKE(f5,f4,16,b8,62,07,4b,28,92,7a,48,c3,ba,7d,51,ff) 42 | #define SD_MESSAGE_SYSTEMD_UDEV_SETTLE_DEPRECATED SD_ID128_MAKE(1c,04,54,c1,bd,22,41,e0,ac,6f,ef,b4,bc,63,14,33) 43 | #define SD_MESSAGE_SYSTEM_UNDOCKED SD_ID128_MAKE(51,e1,71,bd,58,52,48,56,81,10,14,4c,51,7c,ca,53) 44 | #define SD_MESSAGE_TAINTED SD_ID128_MAKE(50,87,6a,9d,b0,0f,4c,40,bd,e1,a2,ad,38,1c,3a,1b) 45 | #define SD_MESSAGE_TIME_CHANGE SD_ID128_MAKE(c7,a7,87,07,9b,35,4e,aa,a9,e7,7b,37,18,93,cd,27) 46 | #define SD_MESSAGE_TIME_SYNC SD_ID128_MAKE(7c,8a,41,f3,7b,76,49,41,a0,e1,78,0b,1b,e2,f0,37) 47 | #define SD_MESSAGE_TIMEZONE_CHANGE SD_ID128_MAKE(45,f8,2f,4a,ef,7a,4b,bf,94,2c,e8,61,d1,f2,09,90) 48 | #define SD_MESSAGE_TRUNCATED_CORE SD_ID128_MAKE(5a,ad,d8,e9,54,dc,4b,1a,8c,95,4d,63,fd,9e,11,37) 49 | #define SD_MESSAGE_UNIT_FAILED SD_ID128_MAKE(be,02,cf,68,55,d2,42,8b,a4,0d,f7,e9,d0,22,f0,3d) 50 | #define SD_MESSAGE_UNIT_FAILURE_RESULT SD_ID128_MAKE(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c) 51 | #define SD_MESSAGE_UNIT_OOMD_KILL SD_ID128_MAKE(d9,89,61,1b,15,e4,4c,9d,bf,31,e3,c8,12,56,e4,ed) 52 | #define SD_MESSAGE_UNIT_OUT_OF_MEMORY SD_ID128_MAKE(fe,6f,aa,94,e7,77,46,63,a0,da,52,71,78,91,d8,ef) 53 | #define SD_MESSAGE_UNIT_PROCESS_EXIT SD_ID128_MAKE(98,e3,22,20,3f,7a,4e,d2,90,d0,9f,e0,3c,09,fe,15) 54 | #define SD_MESSAGE_UNIT_RELOADED SD_ID128_MAKE(7b,05,eb,c6,68,38,42,22,ba,a8,88,11,79,cf,da,54) 55 | #define SD_MESSAGE_UNIT_RELOADING SD_ID128_MAKE(d3,4d,03,7f,ff,18,47,e6,ae,66,9a,37,0e,69,47,25) 56 | #define SD_MESSAGE_UNIT_RESOURCES SD_ID128_MAKE(ae,8f,7b,86,6b,03,47,b9,af,31,fe,1c,80,b1,27,c0) 57 | #define SD_MESSAGE_UNIT_RESTART_SCHEDULED SD_ID128_MAKE(5e,b0,34,94,b6,58,48,70,a5,36,b3,37,29,08,09,b3) 58 | #define SD_MESSAGE_UNIT_SKIPPED SD_ID128_MAKE(0e,42,84,a0,ca,ca,4b,fc,81,c0,bb,67,86,97,26,73) 59 | #define SD_MESSAGE_UNIT_STARTED SD_ID128_MAKE(39,f5,34,79,d3,a0,45,ac,8e,11,78,62,48,23,1f,bf) 60 | #define SD_MESSAGE_UNIT_STARTING SD_ID128_MAKE(7d,49,58,e8,42,da,4a,75,8f,6c,1c,dc,7b,36,dc,c5) 61 | #define SD_MESSAGE_UNIT_STOPPED SD_ID128_MAKE(9d,1a,aa,27,d6,01,40,bd,96,36,54,38,aa,d2,02,86) 62 | #define SD_MESSAGE_UNIT_STOPPING SD_ID128_MAKE(de,5b,42,6a,63,be,47,a7,b6,ac,3e,aa,c8,2e,2f,6f) 63 | #define SD_MESSAGE_UNIT_SUCCESS SD_ID128_MAKE(7a,d2,d1,89,f7,e9,4e,70,a3,8c,78,13,54,91,24,48) 64 | #define SD_MESSAGE_UNSAFE_USER_NAME SD_ID128_MAKE(b6,1f,da,c6,12,e9,4b,91,82,28,5b,99,88,43,06,1f) 65 | #define SD_MESSAGE_USER_STARTUP_FINISHED SD_ID128_MAKE(ee,d0,0a,68,ff,d8,4e,31,88,21,05,fd,97,3a,bd,d1) 66 | -------------------------------------------------------------------------------- /systemd/id128.c: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | /*** 4 | 5 | Copyright 2013 Zbigniew Jędrzejewski-Szmek 6 | 7 | python-systemd is free software; you can redistribute it and/or modify it 8 | under the terms of the GNU Lesser General Public License as published by 9 | the Free Software Foundation; either version 2.1 of the License, or 10 | (at your option) any later version. 11 | 12 | python-systemd is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public License 18 | along with python-systemd; If not, see . 19 | ***/ 20 | 21 | #include 22 | 23 | /* Our include is first, so that our defines are replaced by the ones 24 | * from the system header. If the system header has the same definitions 25 | * (or does not have them at all), this replacement is silent. If the 26 | * system header has a different definition, we get a warning. A warning 27 | * means that the system headers changed incompatibly, and we should update 28 | * our definition. 29 | */ 30 | #include "id128-defines.h" 31 | #include 32 | 33 | #include "pyutil.h" 34 | #include "macro.h" 35 | 36 | #define HAVE_SD_ID128_GET_MACHINE_APP_SPECIFIC (LIBSYSTEMD_VERSION >= 240) 37 | 38 | PyDoc_STRVAR(module__doc__, 39 | "Python interface to the libsystemd-id128 library.\n\n" 40 | "Provides SD_MESSAGE_* constants and functions to query and generate\n" 41 | "128-bit unique identifiers." 42 | ); 43 | 44 | PyDoc_STRVAR(randomize__doc__, 45 | "randomize() -> UUID\n\n" 46 | "Return a new random 128-bit unique identifier.\n" 47 | "Wraps sd_id128_randomize(3)." 48 | ); 49 | 50 | PyDoc_STRVAR(get_machine__doc__, 51 | "get_machine() -> UUID\n\n" 52 | "Return a 128-bit unique identifier for this machine.\n" 53 | "Wraps sd_id128_get_machine(3)." 54 | ); 55 | 56 | PyDoc_STRVAR(get_machine_app_specific__doc__, 57 | "get_machine_app_specific(UUID) -> UUID\n\n" 58 | "Return a 128-bit unique identifier for this application and machine.\n" 59 | "Wraps sd_id128_get_machine_app_specific(3)." 60 | ); 61 | 62 | PyDoc_STRVAR(get_boot__doc__, 63 | "get_boot() -> UUID\n\n" 64 | "Return a 128-bit unique identifier for this boot.\n" 65 | "Wraps sd_id128_get_boot(3)." 66 | ); 67 | 68 | static PyObject* make_uuid(sd_id128_t id) { 69 | _cleanup_Py_DECREF_ PyObject 70 | *uuid = NULL, *UUID = NULL, *bytes = NULL, 71 | *args = NULL, *kwargs = NULL; 72 | 73 | uuid = PyImport_ImportModule("uuid"); 74 | if (!uuid) 75 | return NULL; 76 | 77 | UUID = PyObject_GetAttrString(uuid, "UUID"); 78 | bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); 79 | args = Py_BuildValue("()"); 80 | kwargs = PyDict_New(); 81 | if (!UUID || !bytes || !args || !kwargs) 82 | return NULL; 83 | 84 | if (PyDict_SetItemString(kwargs, "bytes", bytes) < 0) 85 | return NULL; 86 | 87 | return PyObject_Call(UUID, args, kwargs); 88 | } 89 | 90 | #define helper(name) \ 91 | static PyObject *name(PyObject *self, PyObject *args) { \ 92 | sd_id128_t id; \ 93 | int r; \ 94 | \ 95 | assert(!args); \ 96 | \ 97 | r = sd_id128_##name(&id); \ 98 | if (r < 0) { \ 99 | errno = -r; \ 100 | return PyErr_SetFromErrno(PyExc_IOError); \ 101 | } \ 102 | \ 103 | return make_uuid(id); \ 104 | } 105 | 106 | helper(randomize) 107 | helper(get_machine) 108 | helper(get_boot) 109 | 110 | static PyObject *get_machine_app_specific(PyObject *self, PyObject *args) { 111 | _cleanup_Py_DECREF_ PyObject *uuid_bytes = NULL; 112 | 113 | uuid_bytes = PyObject_GetAttrString(args, "bytes"); 114 | if (!uuid_bytes) 115 | return NULL; 116 | 117 | #if HAVE_SD_ID128_GET_MACHINE_APP_SPECIFIC 118 | Py_buffer buffer; 119 | sd_id128_t app_id; 120 | int r; 121 | 122 | r = PyObject_GetBuffer(uuid_bytes, &buffer, 0); 123 | if (r == -1) 124 | return NULL; 125 | 126 | if (buffer.len != sizeof(sd_id128_t)) { 127 | PyBuffer_Release(&buffer); 128 | return NULL; 129 | } 130 | 131 | r = sd_id128_get_machine_app_specific(*(sd_id128_t*)buffer.buf, &app_id); 132 | PyBuffer_Release(&buffer); 133 | if (r < 0) { 134 | errno = -r; 135 | return PyErr_SetFromErrno(PyExc_IOError); 136 | } 137 | 138 | return make_uuid(app_id); 139 | 140 | #else 141 | set_error(-ENOSYS, NULL, "Compiled without support for sd_id128_get_machine_app_specific"); 142 | return NULL; 143 | #endif 144 | } 145 | 146 | static PyMethodDef methods[] = { 147 | { "randomize", randomize, METH_NOARGS, randomize__doc__}, 148 | { "get_machine", get_machine, METH_NOARGS, get_machine__doc__}, 149 | { "get_machine_app_specific", get_machine_app_specific, METH_O, get_machine_app_specific__doc__}, 150 | { "get_boot", get_boot, METH_NOARGS, get_boot__doc__}, 151 | {} /* Sentinel */ 152 | }; 153 | 154 | static int add_id(PyObject *module, const char* name, sd_id128_t id) { 155 | PyObject *obj; 156 | 157 | obj = make_uuid(id); 158 | if (!obj) 159 | return -1; 160 | 161 | return PyModule_AddObject(module, name, obj); 162 | } 163 | 164 | static struct PyModuleDef module = { 165 | PyModuleDef_HEAD_INIT, 166 | .m_name = "id128", /* name of module */ 167 | .m_doc = module__doc__, /* module documentation */ 168 | .m_size = -1, /* size of per-interpreter state of the module */ 169 | .m_methods = methods, 170 | }; 171 | 172 | DISABLE_WARNING_MISSING_PROTOTYPES; 173 | PyMODINIT_FUNC PyInit_id128(void) { 174 | PyObject *m; 175 | 176 | m = PyModule_Create(&module); 177 | if (!m) 178 | return NULL; 179 | 180 | if ( /* a series of lines like 'add_id() ||' follow */ 181 | #define JOINER || 182 | #include "id128-constants.h" 183 | #undef JOINER 184 | PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { 185 | Py_DECREF(m); 186 | return NULL; 187 | } 188 | 189 | return m; 190 | } 191 | REENABLE_WARNING; 192 | -------------------------------------------------------------------------------- /systemd/journal.py: -------------------------------------------------------------------------------- 1 | # -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */ 2 | # 3 | # 4 | # Copyright 2012 David Strauss 5 | # Copyright 2012 Zbigniew Jędrzejewski-Szmek 6 | # Copyright 2012 Marti Raudsepp 7 | # 8 | # python-systemd is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU Lesser General Public License as published by 10 | # the Free Software Foundation; either version 2.1 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # python-systemd is distributed in the hope that it will be useful, but 14 | # WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | # Lesser General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public License 19 | # along with python-systemd; If not, see . 20 | 21 | from __future__ import division 22 | 23 | import sys as _sys 24 | import datetime as _datetime 25 | import uuid as _uuid 26 | import traceback as _traceback 27 | import os as _os 28 | import logging as _logging 29 | from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, 30 | LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) 31 | 32 | from ._journal import __version__, sendv, stream_fd 33 | from ._reader import (_Reader, NOP, APPEND, INVALIDATE, 34 | LOCAL_ONLY, RUNTIME_ONLY, 35 | SYSTEM, SYSTEM_ONLY, CURRENT_USER, 36 | OS_ROOT, 37 | _get_catalog, Monotonic) 38 | from . import id128 as _id128 39 | 40 | 41 | def _convert_monotonic(m): 42 | return Monotonic((_datetime.timedelta(microseconds=m[0]), 43 | _uuid.UUID(bytes=m[1]))) 44 | 45 | 46 | def _convert_source_monotonic(s): 47 | return _datetime.timedelta(microseconds=int(s)) 48 | 49 | try: 50 | _LOCAL_TIMEZONE = _datetime.datetime.now().astimezone().tzinfo 51 | except TypeError: 52 | _LOCAL_TIMEZONE = None 53 | 54 | def _convert_realtime(t): 55 | return _datetime.datetime.fromtimestamp(t / 1000000, _LOCAL_TIMEZONE) 56 | 57 | def _convert_timestamp(s): 58 | return _datetime.datetime.fromtimestamp(int(s) / 1000000, _LOCAL_TIMEZONE) 59 | 60 | 61 | def _convert_trivial(x): 62 | return x 63 | 64 | 65 | def _convert_uuid(s): 66 | return _uuid.UUID(s.decode()) 67 | 68 | 69 | DEFAULT_CONVERTERS = { 70 | 'MESSAGE_ID': _convert_uuid, 71 | '_MACHINE_ID': _convert_uuid, 72 | '_BOOT_ID': _convert_uuid, 73 | 'PRIORITY': int, 74 | 'LEADER': int, 75 | 'SESSION_ID': int, 76 | 'USERSPACE_USEC': int, 77 | 'INITRD_USEC': int, 78 | 'KERNEL_USEC': int, 79 | '_UID': int, 80 | '_GID': int, 81 | '_PID': int, 82 | 'SYSLOG_FACILITY': int, 83 | 'SYSLOG_PID': int, 84 | '_AUDIT_SESSION': int, 85 | '_AUDIT_LOGINUID': int, 86 | '_SYSTEMD_SESSION': int, 87 | '_SYSTEMD_OWNER_UID': int, 88 | 'CODE_LINE': int, 89 | 'ERRNO': int, 90 | 'EXIT_STATUS': int, 91 | '_SOURCE_REALTIME_TIMESTAMP': _convert_timestamp, 92 | '__REALTIME_TIMESTAMP': _convert_realtime, 93 | '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic, 94 | '__MONOTONIC_TIMESTAMP': _convert_monotonic, 95 | '__CURSOR': _convert_trivial, 96 | 'COREDUMP': bytes, 97 | 'COREDUMP_PID': int, 98 | 'COREDUMP_UID': int, 99 | 'COREDUMP_GID': int, 100 | 'COREDUMP_SESSION': int, 101 | 'COREDUMP_SIGNAL': int, 102 | 'COREDUMP_TIMESTAMP': _convert_timestamp, 103 | } 104 | 105 | _IDENT_CHARACTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_0123456789') 106 | 107 | 108 | def _valid_field_name(s): 109 | return not (set(s) - _IDENT_CHARACTER) 110 | 111 | 112 | class Reader(_Reader): 113 | """Access systemd journal entries. 114 | 115 | Entries are subject to filtering and limits, see `add_match`, `this_boot`, 116 | `this_machine` functions and the `data_treshold` attribute. 117 | 118 | Note that in order to access the system journal, a non-root user must have 119 | the necessary privileges, see journalctl(1) for details. Unprivileged users 120 | can access only their own journal. 121 | 122 | Example usage to print out all informational or higher level messages for 123 | systemd-udevd for this boot: 124 | 125 | >>> from systemd import journal 126 | >>> j = journal.Reader() 127 | >>> j.this_boot() 128 | >>> j.log_level(journal.LOG_INFO) 129 | >>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service") 130 | >>> for entry in j: # doctest: +SKIP 131 | ... print(entry['MESSAGE']) 132 | starting version ... 133 | 134 | See systemd.journal-fields(7) for more info on typical fields found in the 135 | journal. 136 | 137 | """ 138 | def __init__(self, flags=None, path=None, files=None, converters=None, namespace=None): 139 | """Create a new Reader. 140 | 141 | Argument `flags` defines the open flags of the journal, which can be one 142 | of, or ORed combination of constants: LOCAL_ONLY (default) opens journal 143 | on local machine only; RUNTIME_ONLY opens only volatile journal files; 144 | and SYSTEM_ONLY opens only journal files of system services and the kernel. 145 | 146 | Argument `path` is the directory of journal files, either a file system 147 | path or a file descriptor. Specify `namespace` to read from specific journal 148 | namespace. Note that `flags`, `path`, `files` and `namespace` are exclusive. 149 | 150 | Argument `converters` is a dictionary which updates the 151 | DEFAULT_CONVERTERS to convert journal field values. Field names are used 152 | as keys into this dictionary. The values must be single argument 153 | functions, which take a `bytes` object and return a converted 154 | value. When there's no entry for a field name, then the default UTF-8 155 | decoding will be attempted. If the conversion fails with a ValueError, 156 | unconverted bytes object will be returned. (Note that ValueEror is a 157 | superclass of UnicodeDecodeError). 158 | 159 | Reader implements the context manager protocol: the journal will be 160 | closed when exiting the block. 161 | """ 162 | if flags is None: 163 | if path is None and files is None: 164 | # This mimics journalctl behaviour of default to local journal only 165 | flags = LOCAL_ONLY 166 | else: 167 | flags = 0 168 | 169 | super(Reader, self).__init__(flags, path, files, namespace) 170 | self.converters = DEFAULT_CONVERTERS.copy() 171 | if converters is not None: 172 | self.converters.update(converters) 173 | 174 | def _convert_field(self, key, value): 175 | """Convert value using self.converters[key]. 176 | 177 | If `key` is not present in self.converters, a standard unicode decoding 178 | will be attempted. If the conversion (either key-specific or the 179 | default one) fails with a ValueError, the original bytes object will be 180 | returned. 181 | """ 182 | convert = self.converters.get(key, bytes.decode) 183 | try: 184 | return convert(value) 185 | except ValueError: 186 | # Leave in default bytes 187 | return value 188 | 189 | def _convert_entry(self, entry): 190 | """Convert entire journal entry utilising _convert_field.""" 191 | result = {} 192 | for key, value in entry.items(): 193 | if isinstance(value, list): 194 | result[key] = [self._convert_field(key, val) for val in value] 195 | else: 196 | result[key] = self._convert_field(key, value) 197 | return result 198 | 199 | def __iter__(self): 200 | """Return self. 201 | 202 | Part of the iterator protocol. 203 | """ 204 | return self 205 | 206 | def __next__(self): 207 | """Return the next entry in the journal. 208 | 209 | Returns self.get_next() or raises StopIteration. 210 | 211 | Part of the iterator protocol. 212 | """ 213 | ans = self.get_next() 214 | if ans: 215 | return ans 216 | else: 217 | raise StopIteration() 218 | 219 | def add_match(self, *args, **kwargs): 220 | """Add one or more matches to the filter journal log entries. 221 | 222 | All matches of different field are combined with logical AND, and 223 | matches of the same field are automatically combined with logical OR. 224 | Matches can be passed as strings of form "FIELD=value", or keyword 225 | arguments FIELD="value". 226 | """ 227 | args = list(args) 228 | args.extend(_make_line(key, val) for key, val in kwargs.items()) 229 | for arg in args: 230 | super(Reader, self).add_match(arg) 231 | 232 | def get_next(self, skip=1): 233 | r"""Return the next log entry as a dictionary. 234 | 235 | Entries will be processed with converters specified during Reader 236 | creation. 237 | 238 | Optional `skip` value will return the `skip`-th log entry. 239 | 240 | Currently a standard dictionary of fields is returned, but in the 241 | future this might be changed to a different mapping type, so the 242 | calling code should not make assumptions about a specific type. 243 | """ 244 | if super(Reader, self)._next(skip): 245 | entry = super(Reader, self)._get_all() 246 | if entry: 247 | entry['__REALTIME_TIMESTAMP'] = self._get_realtime() 248 | entry['__MONOTONIC_TIMESTAMP'] = self._get_monotonic() 249 | entry['__CURSOR'] = self._get_cursor() 250 | return self._convert_entry(entry) 251 | return dict() 252 | 253 | def get_previous(self, skip=1): 254 | r"""Return the previous log entry. 255 | 256 | Equivalent to get_next(-skip). 257 | 258 | Optional `skip` value will return the -`skip`-th log entry. 259 | 260 | Entries will be processed with converters specified during Reader 261 | creation. 262 | 263 | Currently a standard dictionary of fields is returned, but in the 264 | future this might be changed to a different mapping type, so the 265 | calling code should not make assumptions about a specific type. 266 | """ 267 | return self.get_next(-skip) 268 | 269 | def query_unique(self, field): 270 | """Return a list of unique values appearing in the journal for the given 271 | `field`. 272 | 273 | Note this does not respect any journal matches. 274 | 275 | Entries will be processed with converters specified during 276 | Reader creation. 277 | """ 278 | return set(self._convert_field(field, value) 279 | for value in super(Reader, self).query_unique(field)) 280 | 281 | def wait(self, timeout=None): 282 | """Wait for a change in the journal. 283 | 284 | `timeout` is the maximum time in seconds to wait, or None which 285 | means to wait forever. 286 | 287 | Returns one of NOP (no change), APPEND (new entries have been added to 288 | the end of the journal), or INVALIDATE (journal files have been added or 289 | removed). 290 | """ 291 | us = -1 if timeout is None else int(timeout * 1000000) 292 | return super(Reader, self).wait(us) 293 | 294 | def seek_realtime(self, realtime): 295 | """Seek to a matching journal entry nearest to `timestamp` time. 296 | 297 | Argument `realtime` must be either an integer UNIX timestamp (in 298 | microseconds since the beginning of the UNIX epoch), or an float UNIX 299 | timestamp (in seconds since the beginning of the UNIX epoch), or a 300 | datetime.datetime instance. The integer form is deprecated. 301 | 302 | >>> import time 303 | >>> from systemd import journal 304 | 305 | >>> yesterday = time.time() - 24 * 60**2 306 | >>> j = journal.Reader() 307 | >>> j.seek_realtime(yesterday) 308 | """ 309 | if isinstance(realtime, _datetime.datetime): 310 | try: 311 | realtime = realtime.astimezone() 312 | except TypeError: 313 | # With python2: Required argument 'tz' (pos 1) not found 314 | pass 315 | 316 | realtime = int(float(realtime.strftime("%s.%f")) * 1000000) 317 | elif not isinstance(realtime, int): 318 | realtime = int(realtime * 1000000) 319 | return super(Reader, self).seek_realtime(realtime) 320 | 321 | def get_start(self): 322 | start = super(Reader, self)._get_start() 323 | return _convert_realtime(start) 324 | 325 | def get_end(self): 326 | end = super(Reader, self)._get_end() 327 | return _convert_realtime(end) 328 | 329 | def seek_monotonic(self, monotonic, bootid=None): 330 | """Seek to a matching journal entry nearest to `monotonic` time. 331 | 332 | Argument `monotonic` is a timestamp from boot in either seconds or a 333 | datetime.timedelta instance. Argument `bootid` is a string or UUID 334 | representing which boot the monotonic time is reference to. Defaults to 335 | current bootid. 336 | """ 337 | if isinstance(monotonic, _datetime.timedelta): 338 | monotonic = monotonic.total_seconds() 339 | monotonic = int(monotonic * 1000000) 340 | if isinstance(bootid, _uuid.UUID): 341 | bootid = bootid.hex 342 | return super(Reader, self).seek_monotonic(monotonic, bootid) 343 | 344 | def log_level(self, level): 345 | """Set maximum log `level` by setting matches for PRIORITY. 346 | """ 347 | if 0 <= level <= 7: 348 | for i in range(level+1): 349 | self.add_match(PRIORITY="%d" % i) 350 | else: 351 | raise ValueError("Log level must be 0 <= level <= 7") 352 | 353 | def messageid_match(self, messageid): 354 | """Add match for log entries with specified `messageid`. 355 | 356 | `messageid` can be string of hexadicimal digits or a UUID 357 | instance. Standard message IDs can be found in systemd.id128. 358 | 359 | Equivalent to add_match(MESSAGE_ID=`messageid`). 360 | """ 361 | if isinstance(messageid, _uuid.UUID): 362 | messageid = messageid.hex 363 | self.add_match(MESSAGE_ID=messageid) 364 | 365 | def this_boot(self, bootid=None): 366 | """Add match for _BOOT_ID for current boot or the specified boot ID. 367 | 368 | If specified, bootid should be either a UUID or a 32 digit hex number. 369 | 370 | Equivalent to add_match(_BOOT_ID='bootid'). 371 | """ 372 | if bootid is None: 373 | bootid = _id128.get_boot().hex 374 | else: 375 | bootid = getattr(bootid, 'hex', bootid) 376 | self.add_match(_BOOT_ID=bootid) 377 | 378 | def this_machine(self, machineid=None): 379 | """Add match for _MACHINE_ID equal to the ID of this machine. 380 | 381 | If specified, machineid should be either a UUID or a 32 digit hex 382 | number. 383 | 384 | Equivalent to add_match(_MACHINE_ID='machineid'). 385 | """ 386 | if machineid is None: 387 | machineid = _id128.get_machine().hex 388 | else: 389 | machineid = getattr(machineid, 'hex', machineid) 390 | self.add_match(_MACHINE_ID=machineid) 391 | 392 | 393 | def get_catalog(mid): 394 | """Return catalog entry for the specified ID. 395 | 396 | `mid` should be either a UUID or a 32 digit hex number. 397 | """ 398 | if isinstance(mid, _uuid.UUID): 399 | mid = mid.hex 400 | return _get_catalog(mid) 401 | 402 | 403 | def _make_line(field, value): 404 | if isinstance(value, bytes): 405 | return field.encode('utf-8') + b'=' + value 406 | elif isinstance(value, str): 407 | return field + '=' + value 408 | else: 409 | return field + '=' + str(value) 410 | 411 | 412 | def send(MESSAGE, MESSAGE_ID=None, 413 | CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None, 414 | **kwargs): 415 | r"""Send a message to the journal. 416 | 417 | >>> from systemd import journal 418 | >>> journal.send('Hello world') 419 | >>> journal.send('Hello, again, world', FIELD2='Greetings!') 420 | >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef') 421 | 422 | Value of the MESSAGE argument will be used for the MESSAGE= field. MESSAGE 423 | must be a string and will be sent as UTF-8 to the journal. 424 | 425 | MESSAGE_ID can be given to uniquely identify the type of message. It must be 426 | a string or a uuid.UUID object. 427 | 428 | CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to identify the caller. 429 | Unless at least on of the three is given, values are extracted from the 430 | stack frame of the caller of send(). CODE_FILE and CODE_FUNC must be 431 | strings, CODE_LINE must be an integer. 432 | 433 | Additional fields for the journal entry can only be specified as keyword 434 | arguments. The payload can be either a string or bytes. A string will be 435 | sent as UTF-8, and bytes will be sent as-is to the journal. 436 | 437 | Other useful fields include PRIORITY, SYSLOG_FACILITY, SYSLOG_IDENTIFIER, 438 | SYSLOG_PID. 439 | """ 440 | 441 | args = ['MESSAGE=' + MESSAGE] 442 | 443 | if MESSAGE_ID is not None: 444 | id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID) 445 | args.append('MESSAGE_ID=' + id) 446 | 447 | if CODE_LINE is CODE_FILE is CODE_FUNC is None: 448 | CODE_FILE, CODE_LINE, CODE_FUNC = _traceback.extract_stack(limit=2)[0][:3] 449 | if CODE_FILE is not None: 450 | args.append('CODE_FILE=' + CODE_FILE) 451 | if CODE_LINE is not None: 452 | args.append('CODE_LINE={:d}'.format(CODE_LINE)) 453 | if CODE_FUNC is not None: 454 | args.append('CODE_FUNC=' + CODE_FUNC) 455 | 456 | args.extend(_make_line(key, val) for key, val in kwargs.items()) 457 | return sendv(*args) 458 | 459 | 460 | def stream(identifier=None, priority=LOG_INFO, level_prefix=False): 461 | r"""Return a file object wrapping a stream to journal. 462 | 463 | Log messages written to this file as simple newline sepearted text strings 464 | are written to the journal. 465 | 466 | The file will be line buffered, so messages are actually sent after a 467 | newline character is written. 468 | 469 | >>> from systemd import journal 470 | >>> stream = journal.stream('myapp') # doctest: +SKIP 471 | >>> res = stream.write('message...\n') # doctest: +SKIP 472 | 473 | will produce the following message in the journal:: 474 | 475 | PRIORITY=7 476 | SYSLOG_IDENTIFIER=myapp 477 | MESSAGE=message... 478 | 479 | If identifier is None, a suitable default based on sys.argv[0] will be used. 480 | 481 | This interface can be used conveniently with the print function: 482 | 483 | >>> from __future__ import print_function 484 | >>> stream = journal.stream() # doctest: +SKIP 485 | >>> print('message...', file=stream) # doctest: +SKIP 486 | 487 | priority is the syslog priority, one of `LOG_EMERG`, `LOG_ALERT`, 488 | `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`, `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`. 489 | 490 | level_prefix is a boolean. If true, kernel-style log priority level prefixes 491 | (such as '<1>') are interpreted. See sd-daemon(3) for more information. 492 | """ 493 | 494 | if identifier is None: 495 | if not _sys.argv or not _sys.argv[0] or _sys.argv[0] == '-c': 496 | identifier = 'python' 497 | else: 498 | identifier = _sys.argv[0] 499 | 500 | fd = stream_fd(identifier, priority, level_prefix) 501 | return _os.fdopen(fd, 'w', 1) 502 | 503 | 504 | class JournalHandler(_logging.Handler): 505 | """Journal handler class for the Python logging framework. 506 | 507 | Please see the Python logging module documentation for an overview: 508 | http://docs.python.org/library/logging.html. 509 | 510 | To create a custom logger whose messages go only to journal: 511 | 512 | >>> import logging 513 | >>> log = logging.getLogger('custom_logger_name') 514 | >>> log.propagate = False 515 | >>> log.addHandler(JournalHandler()) 516 | >>> log.warning("Some message: %s", 'detail') 517 | 518 | Note that by default, message levels `INFO` and `DEBUG` are ignored by the 519 | logging framework. To enable those log levels: 520 | 521 | >>> log.setLevel(logging.DEBUG) 522 | 523 | To redirect all logging messages to journal regardless of where they come 524 | from, attach it to the root logger: 525 | 526 | >>> logging.root.addHandler(JournalHandler()) 527 | 528 | For more complex configurations when using `dictConfig` or `fileConfig`, 529 | specify `systemd.journal.JournalHandler` as the handler class. Only 530 | standard handler configuration options are supported: `level`, `formatter`, 531 | `filters`. 532 | 533 | To attach journal MESSAGE_ID, an extra field is supported: 534 | 535 | >>> import uuid 536 | >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF') 537 | >>> log.warning("Message with ID", extra={'MESSAGE_ID': mid}) 538 | 539 | Fields to be attached to all messages sent through this handler can be 540 | specified as keyword arguments. This probably makes sense only for 541 | SYSLOG_IDENTIFIER and similar fields which are constant for the whole 542 | program: 543 | 544 | >>> JournalHandler(SYSLOG_IDENTIFIER='my-cool-app') 545 | <...JournalHandler ...> 546 | 547 | The following journal fields will be sent: `MESSAGE`, `PRIORITY`, 548 | `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`, `CODE_FUNC`, `LOGGER` (name as 549 | supplied to getLogger call), `MESSAGE_ID` (optional, see above), 550 | `SYSLOG_IDENTIFIER` (defaults to sys.argv[0]). 551 | 552 | The function used to actually send messages can be overridden using 553 | the `sender_function` parameter. 554 | """ 555 | 556 | def __init__(self, level=_logging.NOTSET, sender_function=send, **kwargs): 557 | super(JournalHandler, self).__init__(level) 558 | 559 | for name in kwargs: 560 | if not _valid_field_name(name): 561 | raise ValueError('Invalid field name: ' + name) 562 | if 'SYSLOG_IDENTIFIER' not in kwargs: 563 | kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0] 564 | 565 | self.send = sender_function 566 | self._extra = kwargs 567 | 568 | @classmethod 569 | def with_args(cls, config=None): 570 | """Create a JournalHandler with a configuration dictionary 571 | 572 | This creates a JournalHandler instance, but accepts the parameters through 573 | a dictionary that can be specified as a positional argument. This is useful 574 | in contexts like logging.config.fileConfig, where the syntax does not allow 575 | for positional arguments. 576 | 577 | >>> JournalHandler.with_args({'SYSLOG_IDENTIFIER':'my-cool-app'}) 578 | <...JournalHandler ...> 579 | """ 580 | return cls(**(config or {})) 581 | 582 | def emit(self, record): 583 | """Write `record` as a journal event. 584 | 585 | MESSAGE is taken from the message provided by the user, and PRIORITY, 586 | LOGGER, THREAD_NAME, CODE_{FILE,LINE,FUNC} fields are appended 587 | automatically. In addition, record.MESSAGE_ID will be used if present. 588 | """ 589 | try: 590 | msg = self.format(record) 591 | pri = self.map_priority(record.levelno) 592 | # defaults 593 | extras = self._extra.copy() 594 | 595 | # higher priority 596 | if record.exc_text: 597 | extras['EXCEPTION_TEXT'] = record.exc_text 598 | 599 | if record.exc_info: 600 | extras['EXCEPTION_INFO'] = record.exc_info 601 | 602 | if record.args: 603 | extras['CODE_ARGS'] = str(record.args) 604 | 605 | # explicit arguments — highest priority 606 | extras.update(record.__dict__) 607 | 608 | self.send(msg, 609 | PRIORITY=format(pri), 610 | LOGGER=record.name, 611 | THREAD_NAME=record.threadName, 612 | PROCESS_NAME=record.processName, 613 | CODE_FILE=record.pathname, 614 | CODE_LINE=record.lineno, 615 | CODE_FUNC=record.funcName, 616 | **extras) 617 | except Exception: 618 | self.handleError(record) 619 | 620 | @staticmethod 621 | def map_priority(levelno): 622 | """Map logging levels to journald priorities. 623 | 624 | Since Python log level numbers are "sparse", we have to map numbers in 625 | between the standard levels too. 626 | """ 627 | if levelno <= _logging.DEBUG: 628 | return LOG_DEBUG 629 | elif levelno <= _logging.INFO: 630 | return LOG_INFO 631 | elif levelno <= _logging.WARNING: 632 | return LOG_WARNING 633 | elif levelno <= _logging.ERROR: 634 | return LOG_ERR 635 | elif levelno <= _logging.CRITICAL: 636 | return LOG_CRIT 637 | else: 638 | return LOG_ALERT 639 | 640 | mapPriority = map_priority 641 | -------------------------------------------------------------------------------- /systemd/login.c: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | /*** 4 | 5 | Copyright 2013 Zbigniew Jędrzejewski-Szmek 6 | 7 | python-systemd is free software; you can redistribute it and/or modify it 8 | under the terms of the GNU Lesser General Public License as published by 9 | the Free Software Foundation; either version 2.1 of the License, or 10 | (at your option) any later version. 11 | 12 | python-systemd is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public License 18 | along with python-systemd; If not, see . 19 | ***/ 20 | 21 | #define PY_SSIZE_T_CLEAN 22 | #pragma GCC diagnostic push 23 | #pragma GCC diagnostic ignored "-Wredundant-decls" 24 | #include 25 | #pragma GCC diagnostic pop 26 | 27 | #include "systemd/sd-login.h" 28 | #include "pyutil.h" 29 | #include "strv.h" 30 | 31 | PyDoc_STRVAR(module__doc__, 32 | "Python interface to the libsystemd-login library." 33 | ); 34 | 35 | #define helper(name) \ 36 | static PyObject* name(PyObject *self, PyObject *args) { \ 37 | _cleanup_strv_free_ char **list = NULL; \ 38 | int r; \ 39 | PyObject *ans; \ 40 | \ 41 | assert(!args); \ 42 | \ 43 | r = sd_get_##name(&list); \ 44 | if (r < 0) { \ 45 | errno = -r; \ 46 | return PyErr_SetFromErrno(PyExc_IOError); \ 47 | } \ 48 | \ 49 | ans = PyList_New(r); \ 50 | if (!ans) \ 51 | return NULL; \ 52 | \ 53 | for (r--; r >= 0; r--) { \ 54 | PyObject *s = unicode_FromString(list[r]); \ 55 | if (!s) { \ 56 | Py_DECREF(ans); \ 57 | return NULL; \ 58 | } \ 59 | \ 60 | PyList_SetItem(ans, r, s); \ 61 | } \ 62 | \ 63 | return ans; \ 64 | } 65 | 66 | helper(seats) 67 | helper(sessions) 68 | helper(machine_names) 69 | #undef helper 70 | 71 | static PyObject* uids(PyObject *self, PyObject *args) { 72 | _cleanup_free_ uid_t *list = NULL; 73 | int r; 74 | PyObject *ans; 75 | 76 | assert(!args); 77 | 78 | r = sd_get_uids(&list); 79 | if (r < 0) { 80 | errno = -r; 81 | return PyErr_SetFromErrno(PyExc_IOError); 82 | } 83 | 84 | ans = PyList_New(r); 85 | if (!ans) 86 | return NULL; 87 | 88 | for (r--; r >= 0; r--) { 89 | PyObject *s = long_FromLong(list[r]); 90 | if (!s) { 91 | Py_DECREF(ans); 92 | return NULL; 93 | } 94 | 95 | PyList_SetItem(ans, r, s); 96 | } 97 | 98 | return ans; 99 | } 100 | 101 | PyDoc_STRVAR(seats__doc__, 102 | "seats() -> list\n\n" 103 | "Returns a list of currently available local seats.\n" 104 | "Wraps sd_get_seats(3)." 105 | ); 106 | 107 | PyDoc_STRVAR(sessions__doc__, 108 | "sessions() -> list\n\n" 109 | "Returns a list of current login sessions.\n" 110 | "Wraps sd_get_sessions(3)." 111 | ); 112 | 113 | PyDoc_STRVAR(machine_names__doc__, 114 | "machine_names() -> list\n\n" 115 | "Returns a list of currently running virtual machines\n" 116 | "and containers on the system.\n" 117 | "Wraps sd_get_machine_names(3)." 118 | ); 119 | 120 | PyDoc_STRVAR(uids__doc__, 121 | "uids() -> list\n\n" 122 | "Returns a list of uids of users who currently have login sessions.\n" 123 | "Wraps sd_get_uids(3)." 124 | ); 125 | 126 | static PyMethodDef methods[] = { 127 | { "seats", seats, METH_NOARGS, seats__doc__}, 128 | { "sessions", sessions, METH_NOARGS, sessions__doc__}, 129 | { "machine_names", machine_names, METH_NOARGS, machine_names__doc__}, 130 | { "uids", uids, METH_NOARGS, uids__doc__}, 131 | {} /* Sentinel */ 132 | }; 133 | 134 | 135 | typedef struct { 136 | PyObject_HEAD 137 | sd_login_monitor *monitor; 138 | } Monitor; 139 | static PyTypeObject MonitorType; 140 | 141 | static void Monitor_dealloc(Monitor* self) { 142 | sd_login_monitor_unref(self->monitor); 143 | Py_TYPE(self)->tp_free((PyObject*)self); 144 | } 145 | 146 | PyDoc_STRVAR(Monitor__doc__, 147 | "Monitor([category]) -> ...\n\n" 148 | "Monitor may be used to monitor login sessions, users, seats, and virtual\n" 149 | "machines/containers. Monitor provides a file descriptor which can be\n" 150 | "integrated in an external event loop.\n\n" 151 | "See :manpage:`sd_login_monitor_new(3)` for the details about what can be monitored."); 152 | static int Monitor_init(Monitor *self, PyObject *args, PyObject *keywds) { 153 | const char *category = NULL; 154 | int r; 155 | 156 | static const char* const kwlist[] = {"category", NULL}; 157 | if (!PyArg_ParseTupleAndKeywords(args, keywds, "|z:__init__", (char**) kwlist, 158 | &category)) 159 | return -1; 160 | 161 | Py_BEGIN_ALLOW_THREADS 162 | r = sd_login_monitor_new(category, &self->monitor); 163 | Py_END_ALLOW_THREADS 164 | 165 | return set_error(r, NULL, "Invalid category"); 166 | } 167 | 168 | 169 | PyDoc_STRVAR(Monitor_fileno__doc__, 170 | "fileno() -> int\n\n" 171 | "Get a file descriptor to poll for events.\n" 172 | "This method wraps sd_login_monitor_get_fd(3)."); 173 | static PyObject* Monitor_fileno(Monitor *self, PyObject *args) { 174 | int fd = sd_login_monitor_get_fd(self->monitor); 175 | set_error(fd, NULL, NULL); 176 | if (fd < 0) 177 | return NULL; 178 | return long_FromLong(fd); 179 | } 180 | 181 | 182 | PyDoc_STRVAR(Monitor_get_events__doc__, 183 | "get_events() -> int\n\n" 184 | "Returns a mask of poll() events to wait for on the file descriptor returned\n" 185 | "by .fileno().\n\n" 186 | "See :manpage:`sd_login_monitor_get_events(3)` for further discussion."); 187 | static PyObject* Monitor_get_events(Monitor *self, PyObject *args) { 188 | int r = sd_login_monitor_get_events(self->monitor); 189 | set_error(r, NULL, NULL); 190 | if (r < 0) 191 | return NULL; 192 | return long_FromLong(r); 193 | } 194 | 195 | 196 | PyDoc_STRVAR(Monitor_get_timeout__doc__, 197 | "get_timeout() -> int or None\n\n" 198 | "Returns a timeout value for usage in poll(), the time since the\n" 199 | "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" 200 | "is necessary.\n\n" 201 | "The return value must be converted to a relative timeout in\n" 202 | "milliseconds if it is to be used as an argument for poll().\n" 203 | "See :manpage:`sd_login_monitor_get_timeout(3)` for further discussion."); 204 | static PyObject* Monitor_get_timeout(Monitor *self, PyObject *args) { 205 | int r; 206 | uint64_t t; 207 | 208 | r = sd_login_monitor_get_timeout(self->monitor, &t); 209 | set_error(r, NULL, NULL); 210 | if (r < 0) 211 | return NULL; 212 | 213 | if (t == (uint64_t) -1) 214 | Py_RETURN_NONE; 215 | 216 | assert_cc(sizeof(unsigned long long) == sizeof(t)); 217 | return PyLong_FromUnsignedLongLong(t); 218 | } 219 | 220 | 221 | PyDoc_STRVAR(Monitor_get_timeout_ms__doc__, 222 | "get_timeout_ms() -> int\n\n" 223 | "Returns a timeout value suitable for usage in poll(), the value\n" 224 | "returned by .get_timeout() converted to relative ms, or -1 if\n" 225 | "no timeout is necessary."); 226 | static PyObject* Monitor_get_timeout_ms(Monitor *self, PyObject *args) { 227 | int r; 228 | uint64_t t; 229 | 230 | r = sd_login_monitor_get_timeout(self->monitor, &t); 231 | set_error(r, NULL, NULL); 232 | if (r < 0) 233 | return NULL; 234 | 235 | return absolute_timeout(t); 236 | } 237 | 238 | 239 | PyDoc_STRVAR(Monitor_close__doc__, 240 | "close() -> None\n\n" 241 | "Free resources allocated by this Monitor object.\n" 242 | "This method invokes sd_login_monitor_unref().\n" 243 | "See :manpage:`sd_login_monitor_unref(3)`."); 244 | static PyObject* Monitor_close(Monitor *self, PyObject *args) { 245 | assert(self); 246 | assert(!args); 247 | 248 | sd_login_monitor_unref(self->monitor); 249 | self->monitor = NULL; 250 | Py_RETURN_NONE; 251 | } 252 | 253 | 254 | PyDoc_STRVAR(Monitor_flush__doc__, 255 | "flush() -> None\n\n" 256 | "Reset the wakeup state of the monitor object.\n" 257 | "This method invokes sd_login_monitor_flush().\n" 258 | "See :manpage:`sd_login_monitor_flush(3)`."); 259 | static PyObject* Monitor_flush(Monitor *self, PyObject *args) { 260 | assert(self); 261 | assert(!args); 262 | 263 | Py_BEGIN_ALLOW_THREADS 264 | sd_login_monitor_flush(self->monitor); 265 | Py_END_ALLOW_THREADS 266 | Py_RETURN_NONE; 267 | } 268 | 269 | 270 | PyDoc_STRVAR(Monitor___enter____doc__, 271 | "__enter__() -> self\n\n" 272 | "Part of the context manager protocol.\n" 273 | "Returns self.\n"); 274 | static PyObject* Monitor___enter__(PyObject *self, PyObject *args) { 275 | assert(self); 276 | assert(!args); 277 | 278 | Py_INCREF(self); 279 | return self; 280 | } 281 | 282 | 283 | PyDoc_STRVAR(Monitor___exit____doc__, 284 | "__exit__(type, value, traceback) -> None\n\n" 285 | "Part of the context manager protocol.\n" 286 | "Closes the monitor..\n"); 287 | static PyObject* Monitor___exit__(Monitor *self, PyObject *args) { 288 | return Monitor_close(self, args); 289 | } 290 | 291 | 292 | static PyMethodDef Monitor_methods[] = { 293 | {"fileno", (PyCFunction) Monitor_fileno, METH_NOARGS, Monitor_fileno__doc__}, 294 | {"get_events", (PyCFunction) Monitor_get_events, METH_NOARGS, Monitor_get_events__doc__}, 295 | {"get_timeout", (PyCFunction) Monitor_get_timeout, METH_NOARGS, Monitor_get_timeout__doc__}, 296 | {"get_timeout_ms", (PyCFunction) Monitor_get_timeout_ms, METH_NOARGS, Monitor_get_timeout_ms__doc__}, 297 | {"close", (PyCFunction) Monitor_close, METH_NOARGS, Monitor_close__doc__}, 298 | {"flush", (PyCFunction) Monitor_flush, METH_NOARGS, Monitor_flush__doc__}, 299 | {"__enter__", (PyCFunction) Monitor___enter__, METH_NOARGS, Monitor___enter____doc__}, 300 | {"__exit__", (PyCFunction) Monitor___exit__, METH_VARARGS, Monitor___exit____doc__}, 301 | {} /* Sentinel */ 302 | }; 303 | 304 | static PyTypeObject MonitorType = { 305 | PyVarObject_HEAD_INIT(NULL, 0) 306 | .tp_name = "login.Monitor", 307 | .tp_basicsize = sizeof(Monitor), 308 | .tp_dealloc = (destructor) Monitor_dealloc, 309 | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 310 | .tp_doc = Monitor__doc__, 311 | .tp_methods = Monitor_methods, 312 | .tp_init = (initproc) Monitor_init, 313 | .tp_new = PyType_GenericNew, 314 | }; 315 | 316 | static struct PyModuleDef module = { 317 | PyModuleDef_HEAD_INIT, 318 | "login", /* name of module */ 319 | module__doc__, /* module documentation, may be NULL */ 320 | -1, /* size of per-interpreter state of the module */ 321 | methods 322 | }; 323 | 324 | DISABLE_WARNING_MISSING_PROTOTYPES; 325 | PyMODINIT_FUNC PyInit_login(void) { 326 | PyObject *m; 327 | 328 | if (PyType_Ready(&MonitorType) < 0) 329 | return NULL; 330 | 331 | m = PyModule_Create(&module); 332 | if (!m) 333 | return NULL; 334 | 335 | if (PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { 336 | Py_DECREF(m); 337 | return NULL; 338 | } 339 | 340 | Py_INCREF(&MonitorType); 341 | if (PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType)) { 342 | Py_DECREF(&MonitorType); 343 | Py_DECREF(m); 344 | return NULL; 345 | } 346 | 347 | return m; 348 | } 349 | REENABLE_WARNING; 350 | -------------------------------------------------------------------------------- /systemd/macro.h: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | #pragma once 4 | 5 | /*** 6 | 7 | Copyright 2010 Lennart Poettering 8 | 9 | python-systemd is free software; you can redistribute it and/or modify it 10 | under the terms of the GNU Lesser General Public License as published by 11 | the Free Software Foundation; either version 2.1 of the License, or 12 | (at your option) any later version. 13 | 14 | python-systemd is distributed in the hope that it will be useful, but 15 | WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public License 20 | along with python-systemd; If not, see . 21 | ***/ 22 | 23 | #define DISABLE_WARNING_MISSING_PROTOTYPES \ 24 | _Pragma("GCC diagnostic push"); \ 25 | _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") 26 | 27 | #define REENABLE_WARNING \ 28 | _Pragma("GCC diagnostic pop") 29 | 30 | #define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ 31 | static inline void func##p(type *p) { \ 32 | if (*p) \ 33 | func(*p); \ 34 | } \ 35 | struct __useless_struct_to_allow_trailing_semicolon__ 36 | 37 | #define new0(t, n) ((t*) calloc((n), sizeof(t))) 38 | #define alloca0(n) \ 39 | ({ \ 40 | char *_new_; \ 41 | size_t _len_ = n; \ 42 | _new_ = alloca(_len_); \ 43 | (void *) memset(_new_, 0, _len_); \ 44 | }) 45 | 46 | #define _cleanup_(x) __attribute__((cleanup(x))) 47 | 48 | static inline void freep(void *p) { 49 | free(*(void**) p); 50 | } 51 | 52 | #define _cleanup_free_ _cleanup_(freep) 53 | 54 | #if defined(static_assert) 55 | # define assert_cc(expr) \ 56 | static_assert(expr, #expr) 57 | #else 58 | # define assert_cc(expr) 59 | #endif 60 | -------------------------------------------------------------------------------- /systemd/pyutil.c: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | /*** 4 | 5 | Copyright 2013 Zbigniew Jędrzejewski-Szmek 6 | 7 | python-systemd is free software; you can redistribute it and/or modify it 8 | under the terms of the GNU Lesser General Public License as published by 9 | the Free Software Foundation; either version 2.1 of the License, or 10 | (at your option) any later version. 11 | 12 | python-systemd is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public License 18 | along with python-systemd; If not, see . 19 | ***/ 20 | 21 | #include 22 | #include "pyutil.h" 23 | 24 | void cleanup_Py_DECREFp(PyObject **p) { 25 | if (!*p) 26 | return; 27 | 28 | Py_DECREF(*p); 29 | } 30 | 31 | PyObject* absolute_timeout(uint64_t t) { 32 | if (t == (uint64_t) -1) 33 | return PyLong_FromLong(-1); 34 | else { 35 | struct timespec ts; 36 | uint64_t n; 37 | int msec; 38 | 39 | clock_gettime(CLOCK_MONOTONIC, &ts); 40 | n = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; 41 | msec = t > n ? (int) ((t - n + 999) / 1000) : 0; 42 | 43 | return PyLong_FromLong(msec); 44 | } 45 | } 46 | 47 | int set_error(int r, const char* path, const char* invalid_message) { 48 | if (r >= 0) 49 | return r; 50 | if (r == -EINVAL && invalid_message) 51 | PyErr_SetString(PyExc_ValueError, invalid_message); 52 | else if (r == -ENOMEM) 53 | PyErr_SetString(PyExc_MemoryError, "Not enough memory"); 54 | else { 55 | errno = -r; 56 | PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); 57 | } 58 | return -1; 59 | } 60 | 61 | int Unicode_FSConverter(PyObject* obj, void *_result) { 62 | PyObject **result = _result; 63 | 64 | assert(result); 65 | 66 | if (!obj) 67 | /* cleanup: we don't return Py_CLEANUP_SUPPORTED, so 68 | * we can assume that it was PyUnicode_FSConverter. */ 69 | return PyUnicode_FSConverter(obj, result); 70 | 71 | if (obj == Py_None) { 72 | *result = NULL; 73 | return 1; 74 | } 75 | 76 | return PyUnicode_FSConverter(obj, result); 77 | } 78 | -------------------------------------------------------------------------------- /systemd/pyutil.h: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | #pragma once 4 | 5 | /*** 6 | 7 | Copyright 2013 Zbigniew Jędrzejewski-Szmek 8 | 9 | python-systemd is free software; you can redistribute it and/or modify it 10 | under the terms of the GNU Lesser General Public License as published by 11 | the Free Software Foundation; either version 2.1 of the License, or 12 | (at your option) any later version. 13 | 14 | python-systemd is distributed in the hope that it will be useful, but 15 | WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public License 20 | along with python-systemd; If not, see . 21 | ***/ 22 | 23 | #ifndef Py_TYPE 24 | /* avoid duplication warnings from errors in Python 2.7 headers */ 25 | # include 26 | #endif 27 | 28 | void cleanup_Py_DECREFp(PyObject **p); 29 | PyObject* absolute_timeout(uint64_t t); 30 | int set_error(int r, const char* path, const char* invalid_message); 31 | 32 | int Unicode_FSConverter(PyObject* obj, void *_result); 33 | 34 | #define _cleanup_Py_DECREF_ _cleanup_(cleanup_Py_DECREFp) 35 | 36 | # define unicode_FromStringAndSize PyUnicode_FromStringAndSize 37 | # define unicode_FromString PyUnicode_FromString 38 | # define long_FromLong PyLong_FromLong 39 | # define long_FromSize_t PyLong_FromSize_t 40 | # define long_Check PyLong_Check 41 | # define long_AsLong PyLong_AsLong 42 | -------------------------------------------------------------------------------- /systemd/strv.c: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | /*** 4 | 5 | Copyright 2010 Lennart Poettering 6 | 7 | python-systemd is free software; you can redistribute it and/or modify it 8 | under the terms of the GNU Lesser General Public License as published by 9 | the Free Software Foundation; either version 2.1 of the License, or 10 | (at your option) any later version. 11 | 12 | python-systemd is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public License 18 | along with python-systemd; If not, see . 19 | ***/ 20 | 21 | #include 22 | 23 | void strv_clear(char **l) { 24 | char **k; 25 | 26 | if (!l) 27 | return; 28 | 29 | for (k = l; *k; k++) 30 | free(*k); 31 | 32 | *l = NULL; 33 | } 34 | 35 | char **strv_free(char **l) { 36 | strv_clear(l); 37 | free(l); 38 | return NULL; 39 | } 40 | -------------------------------------------------------------------------------- /systemd/strv.h: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | /*** 4 | 5 | Copyright 2010 Lennart Poettering 6 | 7 | python-systemd is free software; you can redistribute it and/or modify it 8 | under the terms of the GNU Lesser General Public License as published by 9 | the Free Software Foundation; either version 2.1 of the License, or 10 | (at your option) any later version. 11 | 12 | python-systemd is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public License 18 | along with python-systemd; If not, see . 19 | ***/ 20 | 21 | #include "macro.h" 22 | 23 | char **strv_free(char **l); 24 | DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); 25 | #define _cleanup_strv_free_ _cleanup_(strv_freep) 26 | -------------------------------------------------------------------------------- /systemd/test/test_daemon.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import posix 4 | import socket 5 | import contextlib 6 | import errno 7 | from systemd.daemon import (booted, 8 | is_fifo, _is_fifo, 9 | is_socket, _is_socket, 10 | is_socket_inet, _is_socket_inet, 11 | is_socket_unix, _is_socket_unix, 12 | is_socket_sockaddr, _is_socket_sockaddr, 13 | is_mq, _is_mq, 14 | listen_fds, listen_fds_with_names, 15 | notify) 16 | 17 | import pytest 18 | 19 | @contextlib.contextmanager 20 | def skip_enosys(): 21 | try: 22 | yield 23 | except OSError as e: 24 | if e.errno == errno.ENOSYS: 25 | pytest.skip() 26 | raise 27 | 28 | @contextlib.contextmanager 29 | def closing_socketpair(family): 30 | pair = socket.socketpair(family) 31 | try: 32 | yield pair 33 | finally: 34 | pair[0].close() 35 | pair[1].close() 36 | 37 | 38 | def test_booted(): 39 | if os.path.exists('/run/systemd/system'): 40 | # assume we are running under systemd 41 | assert booted() 42 | else: 43 | # don't assume anything 44 | assert booted() in {False, True} 45 | 46 | def test__is_fifo(tmpdir): 47 | path = tmpdir.join('test.fifo').strpath 48 | posix.mkfifo(path) 49 | fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK) 50 | 51 | assert _is_fifo(fd, None) 52 | assert _is_fifo(fd, path) 53 | 54 | def test__is_fifo_file(tmpdir): 55 | file = tmpdir.join('test.fifo') 56 | file.write('boo') 57 | path = file.strpath 58 | fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK) 59 | 60 | assert not _is_fifo(fd, None) 61 | assert not _is_fifo(fd, path) 62 | 63 | def test__is_fifo_bad_fd(tmpdir): 64 | path = tmpdir.join('test.fifo').strpath 65 | 66 | with pytest.raises(OSError): 67 | assert not _is_fifo(-1, None) 68 | 69 | with pytest.raises(OSError): 70 | assert not _is_fifo(-1, path) 71 | 72 | def test_is_fifo(tmpdir): 73 | path = tmpdir.join('test.fifo').strpath 74 | posix.mkfifo(path) 75 | fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK) 76 | file = os.fdopen(fd, 'r') 77 | 78 | assert is_fifo(file, None) 79 | assert is_fifo(file, path) 80 | assert is_fifo(fd, None) 81 | assert is_fifo(fd, path) 82 | 83 | def test_is_fifo_file(tmpdir): 84 | file = tmpdir.join('test.fifo') 85 | file.write('boo') 86 | path = file.strpath 87 | fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK) 88 | file = os.fdopen(fd, 'r') 89 | 90 | assert not is_fifo(file, None) 91 | assert not is_fifo(file, path) 92 | assert not is_fifo(fd, None) 93 | assert not is_fifo(fd, path) 94 | 95 | def test_is_fifo_bad_fd(tmpdir): 96 | path = tmpdir.join('test.fifo').strpath 97 | 98 | with pytest.raises(OSError): 99 | assert not is_fifo(-1, None) 100 | 101 | with pytest.raises(OSError): 102 | assert not is_fifo(-1, path) 103 | 104 | def is_mq_wrapper(arg): 105 | try: 106 | return is_mq(arg) 107 | except OSError as error: 108 | # systemd < 227 compatibility 109 | assert error.errno == errno.EBADF 110 | return False 111 | 112 | def _is_mq_wrapper(arg): 113 | try: 114 | return _is_mq(arg) 115 | except OSError as error: 116 | # systemd < 227 compatibility 117 | assert error.errno == errno.EBADF 118 | return False 119 | 120 | def test_no_mismatch(): 121 | with closing_socketpair(socket.AF_UNIX) as pair: 122 | for sock in pair: 123 | assert not is_fifo(sock) 124 | assert not is_mq_wrapper(sock) 125 | assert not is_socket_inet(sock) 126 | with skip_enosys(): 127 | assert not is_socket_sockaddr(sock, '127.0.0.1:2000') 128 | 129 | fd = sock.fileno() 130 | assert not is_fifo(fd) 131 | assert not is_mq_wrapper(fd) 132 | assert not is_socket_inet(fd) 133 | with skip_enosys(): 134 | assert not is_socket_sockaddr(fd, '127.0.0.1:2000') 135 | 136 | assert not _is_fifo(fd) 137 | assert not _is_mq_wrapper(fd) 138 | assert not _is_socket_inet(fd) 139 | with skip_enosys(): 140 | assert not _is_socket_sockaddr(fd, '127.0.0.1:2000') 141 | 142 | def test_is_socket(): 143 | with closing_socketpair(socket.AF_UNIX) as pair: 144 | for sock in pair: 145 | for arg in (sock, sock.fileno()): 146 | assert is_socket(arg) 147 | assert is_socket(arg, socket.AF_UNIX) 148 | assert not is_socket(arg, socket.AF_INET) 149 | assert is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) 150 | assert not is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) 151 | with skip_enosys(): 152 | assert not is_socket_sockaddr(arg, '8.8.8.8:2000', socket.SOCK_DGRAM, 0, 0) 153 | 154 | assert _is_socket(arg) 155 | assert _is_socket(arg, socket.AF_UNIX) 156 | assert not _is_socket(arg, socket.AF_INET) 157 | assert _is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) 158 | assert not _is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) 159 | with skip_enosys(): 160 | assert not _is_socket_sockaddr(arg, '8.8.8.8:2000', socket.SOCK_DGRAM, 0, 0) 161 | 162 | def test_is_socket_sockaddr(): 163 | with contextlib.closing(socket.socket(socket.AF_INET)) as sock: 164 | sock.bind(('127.0.0.1', 0)) 165 | addr, port = sock.getsockname() 166 | port = ':{}'.format(port) 167 | 168 | for listening in (0, 1): 169 | for arg in (sock, sock.fileno()): 170 | with skip_enosys(): 171 | assert is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_STREAM) 172 | with skip_enosys(): 173 | assert is_socket_sockaddr(arg, '127.0.0.1' + port, socket.SOCK_STREAM) 174 | 175 | with skip_enosys(): 176 | assert is_socket_sockaddr(arg, '127.0.0.1' + port, listening=listening) 177 | with skip_enosys(): 178 | assert is_socket_sockaddr(arg, '127.0.0.1' + port, listening=-1) 179 | with skip_enosys(): 180 | assert not is_socket_sockaddr(arg, '127.0.0.1' + port, listening=not listening) 181 | 182 | with pytest.raises(ValueError): 183 | is_socket_sockaddr(arg, '127.0.0.1', flowinfo=123456) 184 | 185 | with skip_enosys(): 186 | assert not is_socket_sockaddr(arg, '129.168.11.11:23', socket.SOCK_STREAM) 187 | with skip_enosys(): 188 | assert not is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) 189 | 190 | with pytest.raises(ValueError): 191 | _is_socket_sockaddr(arg, '127.0.0.1', 0, 123456) 192 | 193 | with skip_enosys(): 194 | assert not _is_socket_sockaddr(arg, '129.168.11.11:23', socket.SOCK_STREAM) 195 | with skip_enosys(): 196 | assert not _is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) 197 | 198 | sock.listen(11) 199 | 200 | def test__is_socket(): 201 | with closing_socketpair(socket.AF_UNIX) as pair: 202 | for sock in pair: 203 | fd = sock.fileno() 204 | assert _is_socket(fd) 205 | assert _is_socket(fd, socket.AF_UNIX) 206 | assert not _is_socket(fd, socket.AF_INET) 207 | assert _is_socket(fd, socket.AF_UNIX, socket.SOCK_STREAM) 208 | assert not _is_socket(fd, socket.AF_INET, socket.SOCK_DGRAM) 209 | 210 | assert _is_socket(fd) 211 | assert _is_socket(fd, socket.AF_UNIX) 212 | assert not _is_socket(fd, socket.AF_INET) 213 | assert _is_socket(fd, socket.AF_UNIX, socket.SOCK_STREAM) 214 | assert not _is_socket(fd, socket.AF_INET, socket.SOCK_DGRAM) 215 | 216 | def test_is_socket_unix(): 217 | with closing_socketpair(socket.AF_UNIX) as pair: 218 | for sock in pair: 219 | for arg in (sock, sock.fileno()): 220 | assert is_socket_unix(arg) 221 | assert not is_socket_unix(arg, path="/no/such/path") 222 | assert is_socket_unix(arg, socket.SOCK_STREAM) 223 | assert not is_socket_unix(arg, socket.SOCK_DGRAM) 224 | 225 | def test__is_socket_unix(): 226 | with closing_socketpair(socket.AF_UNIX) as pair: 227 | for sock in pair: 228 | fd = sock.fileno() 229 | assert _is_socket_unix(fd) 230 | assert not _is_socket_unix(fd, 0, -1, "/no/such/path") 231 | assert _is_socket_unix(fd, socket.SOCK_STREAM) 232 | assert not _is_socket_unix(fd, socket.SOCK_DGRAM) 233 | 234 | def test_listen_fds_no_fds(): 235 | # make sure we have no fds to listen to 236 | os.unsetenv('LISTEN_FDS') 237 | os.unsetenv('LISTEN_PID') 238 | 239 | assert listen_fds() == [] 240 | assert listen_fds(True) == [] 241 | assert listen_fds(False) == [] 242 | 243 | def test_listen_fds(): 244 | os.environ['LISTEN_FDS'] = '3' 245 | os.environ['LISTEN_PID'] = str(os.getpid()) 246 | 247 | assert listen_fds(False) == [3, 4, 5] 248 | assert listen_fds(True) == [3, 4, 5] 249 | assert listen_fds() == [] 250 | 251 | def test_listen_fds_default_unset(): 252 | os.environ['LISTEN_FDS'] = '1' 253 | os.environ['LISTEN_PID'] = str(os.getpid()) 254 | 255 | assert listen_fds(False) == [3] 256 | assert listen_fds() == [3] 257 | assert listen_fds() == [] 258 | 259 | def test_listen_fds_with_names_nothing(): 260 | # make sure we have no fds to listen to, no names 261 | os.unsetenv('LISTEN_FDS') 262 | os.unsetenv('LISTEN_PID') 263 | os.unsetenv('LISTEN_FDNAMES') 264 | 265 | assert listen_fds_with_names() == {} 266 | assert listen_fds_with_names(True) == {} 267 | assert listen_fds_with_names(False) == {} 268 | 269 | def test_listen_fds_with_names_no_names(): 270 | # make sure we have no fds to listen to, no names 271 | os.environ['LISTEN_FDS'] = '1' 272 | os.environ['LISTEN_PID'] = str(os.getpid()) 273 | os.unsetenv('LISTEN_FDNAMES') 274 | 275 | assert listen_fds_with_names(False) == {3: 'unknown'} 276 | assert listen_fds_with_names(True) == {3: 'unknown'} 277 | assert listen_fds_with_names() == {} 278 | 279 | def test_listen_fds_with_names_single(): 280 | # make sure we have no fds to listen to, no names 281 | os.environ['LISTEN_FDS'] = '1' 282 | os.environ['LISTEN_PID'] = str(os.getpid()) 283 | os.environ['LISTEN_FDNAMES'] = 'cmds' 284 | 285 | assert listen_fds_with_names(False) == {3: 'cmds'} 286 | assert listen_fds_with_names() == {3: 'cmds'} 287 | assert listen_fds_with_names(True) == {} 288 | 289 | def test_listen_fds_with_names_multiple(): 290 | # make sure we have no fds to listen to, no names 291 | os.environ['LISTEN_FDS'] = '3' 292 | os.environ['LISTEN_PID'] = str(os.getpid()) 293 | os.environ['LISTEN_FDNAMES'] = 'cmds:data:errs' 294 | 295 | assert listen_fds_with_names(False) == {3: 'cmds', 4: 'data', 5: 'errs'} 296 | assert listen_fds_with_names(True) == {3: 'cmds', 4: 'data', 5: 'errs'} 297 | assert listen_fds_with_names() == {} 298 | 299 | def test_notify_no_socket(): 300 | os.environ.pop('NOTIFY_SOCKET', None) 301 | 302 | assert notify('READY=1') is False 303 | with skip_enosys(): 304 | assert notify('FDSTORE=1', fds=[]) is False 305 | assert notify('FDSTORE=1', fds=[1, 2]) is False 306 | assert notify('FDSTORE=1', pid=os.getpid()) is False 307 | assert notify('FDSTORE=1', pid=os.getpid(), fds=(1,)) is False 308 | 309 | 310 | def test_notify_bad_socket(): 311 | os.environ['NOTIFY_SOCKET'] = '/dev/null' 312 | 313 | with pytest.raises(ConnectionRefusedError): 314 | notify('READY=1') 315 | with pytest.raises(ConnectionRefusedError): 316 | with skip_enosys(): 317 | notify('FDSTORE=1', fds=[]) 318 | with pytest.raises(ConnectionRefusedError): 319 | notify('FDSTORE=1', fds=[1, 2]) 320 | with pytest.raises(ConnectionRefusedError): 321 | notify('FDSTORE=1', pid=os.getpid()) 322 | with pytest.raises(ConnectionRefusedError): 323 | notify('FDSTORE=1', pid=os.getpid(), fds=(1,)) 324 | 325 | def test_notify_with_socket(tmpdir): 326 | path = tmpdir.join('socket').strpath 327 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) 328 | try: 329 | sock.bind(path) 330 | except socket.error as e: 331 | pytest.xfail('failed to bind socket (%s)' % e) 332 | # SO_PASSCRED is not defined in python2.7 333 | SO_PASSCRED = getattr(socket, 'SO_PASSCRED', 16) 334 | sock.setsockopt(socket.SOL_SOCKET, SO_PASSCRED, 1) 335 | os.environ['NOTIFY_SOCKET'] = path 336 | 337 | assert notify('READY=1') 338 | with skip_enosys(): 339 | assert notify('FDSTORE=1', fds=[]) 340 | assert notify('FDSTORE=1', fds=[1, 2]) 341 | assert notify('FDSTORE=1', pid=os.getpid()) 342 | assert notify('FDSTORE=1', pid=os.getpid(), fds=(1,)) 343 | 344 | def test_daemon_notify_memleak(): 345 | # https://github.com/systemd/python-systemd/pull/51 346 | fd = 1 347 | fds = [fd] 348 | ref_cnt = sys.getrefcount(fd) 349 | 350 | try: 351 | notify('', True, 0, fds) 352 | except ConnectionRefusedError: 353 | pass 354 | 355 | assert sys.getrefcount(fd) <= ref_cnt, 'leak' 356 | -------------------------------------------------------------------------------- /systemd/test/test_id128.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import errno 3 | import uuid 4 | import pytest 5 | 6 | from systemd import id128 7 | 8 | @contextlib.contextmanager 9 | def skip_oserror(*errnos): 10 | try: 11 | yield 12 | except (OSError, IOError) as e: 13 | if e.errno in errnos: 14 | pytest.skip() 15 | raise 16 | 17 | 18 | def test_randomize(): 19 | u1 = id128.randomize() 20 | u2 = id128.randomize() 21 | assert u1 != u2 22 | 23 | def test_get_machine(): 24 | # yikes, python2 doesn't know ENOMEDIUM 25 | with skip_oserror(errno.ENOENT, errno.ENOSYS, 123): 26 | u1 = id128.get_machine() 27 | 28 | u2 = id128.get_machine() 29 | assert u1 == u2 30 | 31 | def test_get_machine_app_specific(): 32 | a1 = uuid.uuid1() 33 | a2 = uuid.uuid1() 34 | 35 | with skip_oserror(errno.ENOENT, errno.ENOSYS, 123): 36 | u1 = id128.get_machine_app_specific(a1) 37 | 38 | u2 = id128.get_machine_app_specific(a2) 39 | u3 = id128.get_machine_app_specific(a1) 40 | u4 = id128.get_machine_app_specific(a2) 41 | 42 | assert u1 != u2 43 | assert u1 == u3 44 | assert u2 == u4 45 | 46 | def test_get_boot(): 47 | u1 = id128.get_boot() 48 | u2 = id128.get_boot() 49 | assert u1 == u2 50 | -------------------------------------------------------------------------------- /systemd/test/test_journal.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import contextlib 3 | import datetime 4 | import errno 5 | import logging 6 | import os 7 | import time 8 | import uuid 9 | import sys 10 | import traceback 11 | 12 | from systemd import journal, id128 13 | from systemd.journal import _make_line 14 | 15 | import pytest 16 | 17 | TEST_MID = uuid.UUID('8441372f8dca4ca98694a6091fd8519f') 18 | TEST_MID2 = uuid.UUID('8441370000000000000000001fd85000') 19 | 20 | class MockSender: 21 | def __init__(self): 22 | self.buf = [] 23 | 24 | def send(self, MESSAGE, MESSAGE_ID=None, 25 | CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None, 26 | **kwargs): 27 | args = ['MESSAGE=' + MESSAGE] 28 | 29 | if MESSAGE_ID is not None: 30 | id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID) 31 | args.append('MESSAGE_ID=' + id) 32 | 33 | if CODE_LINE is CODE_FILE is CODE_FUNC is None: 34 | CODE_FILE, CODE_LINE, CODE_FUNC = traceback.extract_stack(limit=2)[0][:3] 35 | if CODE_FILE is not None: 36 | args.append('CODE_FILE=' + CODE_FILE) 37 | if CODE_LINE is not None: 38 | args.append('CODE_LINE={:d}'.format(CODE_LINE)) 39 | if CODE_FUNC is not None: 40 | args.append('CODE_FUNC=' + CODE_FUNC) 41 | 42 | args.extend(_make_line(key, val) for key, val in kwargs.items()) 43 | self.buf.append(args) 44 | 45 | @contextlib.contextmanager 46 | def skip_oserror(code): 47 | try: 48 | yield 49 | except (OSError, IOError) as e: 50 | if e.errno == code: 51 | pytest.skip() 52 | raise 53 | 54 | @contextlib.contextmanager 55 | def skip_valueerror(): 56 | try: 57 | yield 58 | except ValueError: 59 | pytest.skip() 60 | 61 | def test_priorities(): 62 | p = journal.JournalHandler.map_priority 63 | 64 | assert p(logging.NOTSET) == journal.LOG_DEBUG 65 | assert p(logging.DEBUG) == journal.LOG_DEBUG 66 | assert p(logging.DEBUG - 1) == journal.LOG_DEBUG 67 | assert p(logging.DEBUG + 1) == journal.LOG_INFO 68 | assert p(logging.INFO - 1) == journal.LOG_INFO 69 | assert p(logging.INFO) == journal.LOG_INFO 70 | assert p(logging.INFO + 1) == journal.LOG_WARNING 71 | assert p(logging.WARN - 1) == journal.LOG_WARNING 72 | assert p(logging.WARN) == journal.LOG_WARNING 73 | assert p(logging.WARN + 1) == journal.LOG_ERR 74 | assert p(logging.ERROR - 1) == journal.LOG_ERR 75 | assert p(logging.ERROR) == journal.LOG_ERR 76 | assert p(logging.ERROR + 1) == journal.LOG_CRIT 77 | assert p(logging.FATAL) == journal.LOG_CRIT 78 | assert p(logging.CRITICAL) == journal.LOG_CRIT 79 | assert p(logging.CRITICAL + 1) == journal.LOG_ALERT 80 | 81 | 82 | def test_journalhandler_init_exception(): 83 | kw = {' X ':3} 84 | with pytest.raises(ValueError): 85 | journal.JournalHandler(**kw) 86 | with pytest.raises(ValueError): 87 | journal.JournalHandler.with_args(kw) 88 | 89 | def test_journalhandler_init(): 90 | kw = {'X':3, 'X3':4} 91 | journal.JournalHandler(logging.INFO, **kw) 92 | kw['level'] = logging.INFO 93 | journal.JournalHandler.with_args(kw) 94 | 95 | def test_journalhandler_info(): 96 | record = logging.LogRecord('test-logger', logging.INFO, 'testpath', 1, 'test', None, None) 97 | 98 | sender = MockSender() 99 | kw = {'X':3, 'X3':4, 'sender_function': sender.send} 100 | handler = journal.JournalHandler(logging.INFO, **kw) 101 | handler.emit(record) 102 | assert len(sender.buf) == 1 103 | assert 'X=3' in sender.buf[0] 104 | assert 'X3=4' in sender.buf[0] 105 | 106 | sender = MockSender() 107 | handler = journal.JournalHandler.with_args({'level':logging.INFO, 'X':3, 'X3':4, 'sender_function':sender.send}) 108 | handler.emit(record) 109 | assert len(sender.buf) == 1 110 | assert 'X=3' in sender.buf[0] 111 | assert 'X3=4' in sender.buf[0] 112 | 113 | # just check that args==None doesn't cause an error 114 | journal.JournalHandler.with_args() 115 | 116 | def test_journalhandler_no_message_id(): 117 | record = logging.LogRecord('test-logger', logging.INFO, 'testpath', 1, 'test', None, None) 118 | sender = MockSender() 119 | handler = journal.JournalHandler(logging.INFO, sender_function=sender.send) 120 | handler.emit(record) 121 | assert len(sender.buf) == 1 122 | assert all(not m.startswith('MESSAGE_ID=') for m in sender.buf[0]) 123 | 124 | def test_journalhandler_message_id_on_handler(): 125 | record = logging.LogRecord('test-logger', logging.INFO, 'testpath', 1, 'test', None, None) 126 | sender = MockSender() 127 | handler = journal.JournalHandler(logging.INFO, sender_function=sender.send, 128 | MESSAGE_ID=TEST_MID) 129 | handler.emit(record) 130 | assert len(sender.buf) == 1 131 | assert 'MESSAGE_ID=' + TEST_MID.hex in sender.buf[0] 132 | 133 | def test_journalhandler_message_id_on_handler_hex(): 134 | record = logging.LogRecord('test-logger', logging.INFO, 'testpath', 1, 'test', None, None) 135 | sender = MockSender() 136 | handler = journal.JournalHandler(logging.INFO, sender_function=sender.send, 137 | MESSAGE_ID=TEST_MID.hex) 138 | handler.emit(record) 139 | assert len(sender.buf) == 1 140 | assert 'MESSAGE_ID=' + TEST_MID.hex in sender.buf[0] 141 | 142 | def test_journalhandler_message_id_on_message(): 143 | record = logging.LogRecord('test-logger', logging.INFO, 'testpath', 1, 'test', None, None) 144 | record.__dict__['MESSAGE_ID'] = TEST_MID2 145 | sender = MockSender() 146 | handler = journal.JournalHandler(logging.INFO, sender_function=sender.send, 147 | MESSAGE_ID=TEST_MID) 148 | handler.emit(record) 149 | assert len(sender.buf) == 1 150 | assert 'MESSAGE_ID=' + TEST_MID2.hex in sender.buf[0] 151 | 152 | def test_journalhandler_message_id_on_message_hex(): 153 | record = logging.LogRecord('test-logger', logging.INFO, 'testpath', 1, 'test', None, None) 154 | record.__dict__['MESSAGE_ID'] = TEST_MID2.hex 155 | sender = MockSender() 156 | handler = journal.JournalHandler(logging.INFO, sender_function=sender.send, 157 | MESSAGE_ID=TEST_MID) 158 | handler.emit(record) 159 | assert len(sender.buf) == 1 160 | assert 'MESSAGE_ID=' + TEST_MID2.hex in sender.buf[0] 161 | 162 | def test_reader_init_flags(): 163 | j1 = journal.Reader() 164 | j2 = journal.Reader(journal.LOCAL_ONLY) 165 | j3 = journal.Reader(journal.RUNTIME_ONLY) 166 | j4 = journal.Reader(journal.SYSTEM_ONLY) 167 | j5 = journal.Reader(journal.LOCAL_ONLY | journal.RUNTIME_ONLY | journal.SYSTEM_ONLY) 168 | j6 = journal.Reader(0) 169 | 170 | def test_reader_os_root(tmpdir): 171 | with pytest.raises(ValueError): 172 | journal.Reader(journal.OS_ROOT) 173 | with skip_valueerror(): 174 | j1 = journal.Reader(path=tmpdir.strpath, 175 | flags=journal.OS_ROOT) 176 | with skip_valueerror(): 177 | j2 = journal.Reader(path=tmpdir.strpath, 178 | flags=journal.OS_ROOT | journal.CURRENT_USER) 179 | j3 = journal.Reader(path=tmpdir.strpath, 180 | flags=journal.OS_ROOT | journal.SYSTEM_ONLY) 181 | 182 | def test_reader_init_path(tmpdir): 183 | j1 = journal.Reader(path=tmpdir.strpath) 184 | journal.Reader(0, path=tmpdir.strpath) 185 | 186 | j2 = journal.Reader(path=tmpdir.strpath) 187 | journal.Reader(path=tmpdir.strpath) 188 | 189 | def test_reader_init_path_invalid_fd(): 190 | with pytest.raises(OSError): 191 | journal.Reader(0, path=-1) 192 | 193 | def test_reader_init_path_nondirectory_fd(): 194 | with pytest.raises(OSError): 195 | journal.Reader(0, path=0) 196 | 197 | def test_reader_init_path_fd(tmpdir): 198 | fd = os.open(tmpdir.strpath, os.O_RDONLY) 199 | 200 | with skip_oserror(errno.ENOSYS): 201 | j1 = journal.Reader(path=fd) 202 | assert list(j1) == [] 203 | 204 | with skip_valueerror(): 205 | j2 = journal.Reader(journal.SYSTEM, path=fd) 206 | assert list(j2) == [] 207 | 208 | j3 = journal.Reader(journal.CURRENT_USER, path=fd) 209 | assert list(j3) == [] 210 | 211 | def test_reader_as_cm(tmpdir): 212 | j = journal.Reader(path=tmpdir.strpath) 213 | with j: 214 | assert not j.closed 215 | assert j.closed 216 | # make sure that operations on the Reader fail 217 | with pytest.raises(OSError): 218 | next(j) 219 | 220 | def test_reader_messageid_match(tmpdir): 221 | j = journal.Reader(path=tmpdir.strpath) 222 | with j: 223 | j.messageid_match(id128.SD_MESSAGE_JOURNAL_START) 224 | j.messageid_match(id128.SD_MESSAGE_JOURNAL_STOP.hex) 225 | 226 | def test_reader_this_boot(tmpdir): 227 | j = journal.Reader(path=tmpdir.strpath) 228 | with j: 229 | j.this_boot() 230 | j.this_boot(TEST_MID) 231 | j.this_boot(TEST_MID.hex) 232 | 233 | def test_reader_this_machine(tmpdir): 234 | j = journal.Reader(path=tmpdir.strpath) 235 | with j: 236 | try: 237 | j.this_machine() 238 | except OSError: 239 | pass 240 | 241 | j.this_machine(TEST_MID) 242 | j.this_machine(TEST_MID.hex) 243 | 244 | def test_reader_query_unique(tmpdir): 245 | j = journal.Reader(path=tmpdir.strpath) 246 | with j: 247 | with skip_oserror(errno.ENOSYS): 248 | ans = j.query_unique('FOOBAR') 249 | assert isinstance(ans, set) 250 | assert ans == set() 251 | 252 | def test_reader_enumerate_fields(tmpdir): 253 | j = journal.Reader(path=tmpdir.strpath) 254 | with j: 255 | with skip_oserror(errno.ENOSYS): 256 | ans = j.enumerate_fields() 257 | assert isinstance(ans, set) 258 | assert ans == set() 259 | 260 | def test_reader_has_runtime_files(tmpdir): 261 | j = journal.Reader(path=tmpdir.strpath) 262 | with j: 263 | with skip_oserror(errno.ENOSYS): 264 | ans = j.has_runtime_files() 265 | assert ans is False 266 | 267 | def test_reader_has_persistent_files(tmpdir): 268 | j = journal.Reader(path=tmpdir.strpath) 269 | with j: 270 | with skip_oserror(errno.ENOSYS): 271 | ans = j.has_runtime_files() 272 | assert ans is False 273 | 274 | def test_reader_converters(tmpdir): 275 | converters = {'xxx' : lambda arg: 'yyy'} 276 | j = journal.Reader(path=tmpdir.strpath, converters=converters) 277 | 278 | val = j._convert_field('xxx', b'abc') 279 | assert val == 'yyy' 280 | 281 | val = j._convert_field('zzz', b'\200\200') 282 | assert val == b'\200\200' 283 | 284 | def test_reader_convert_entry(tmpdir): 285 | converters = {'x1' : lambda arg: 'yyy', 286 | 'x2' : lambda arg: 'YYY'} 287 | j = journal.Reader(path=tmpdir.strpath, converters=converters) 288 | 289 | val = j._convert_entry({'x1' : b'abc', 290 | 'y1' : b'\200\200', 291 | 'x2' : [b'abc', b'def'], 292 | 'y2' : [b'\200\200', b'\200\201']}) 293 | assert val == {'x1' : 'yyy', 294 | 'y1' : b'\200\200', 295 | 'x2' : ['YYY', 'YYY'], 296 | 'y2' : [b'\200\200', b'\200\201']} 297 | 298 | def test_reader_convert_timestamps(tmpdir): 299 | j = journal.Reader(path=tmpdir.strpath) 300 | 301 | val = j._convert_field('_SOURCE_REALTIME_TIMESTAMP', 1641651559324187) 302 | if sys.version_info >= (3,): 303 | assert val.tzinfo is not None 304 | 305 | val = j._convert_field('__REALTIME_TIMESTAMP', 1641651559324187) 306 | if sys.version_info >= (3,): 307 | assert val.tzinfo is not None 308 | 309 | val = j._convert_field('COREDUMP_TIMESTAMP', 1641651559324187) 310 | if sys.version_info >= (3,): 311 | assert val.tzinfo is not None 312 | 313 | def test_seek_realtime(tmpdir): 314 | j = journal.Reader(path=tmpdir.strpath) 315 | 316 | now = time.time() 317 | j.seek_realtime(now) 318 | 319 | j.seek_realtime(12345) 320 | 321 | long_ago = datetime.datetime(1970, 5, 4) 322 | j.seek_realtime(long_ago) 323 | 324 | def test_journal_stream(): 325 | # This will fail when running in a bare chroot without /run/systemd/journal/stdout 326 | with skip_oserror(errno.ENOENT): 327 | stream = journal.stream('test_journal.py') 328 | 329 | res = stream.write('message...\n') 330 | assert res in (11, None) # Python2 returns None 331 | 332 | print('printed message...', file=stream) 333 | -------------------------------------------------------------------------------- /systemd/test/test_login.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import select 3 | import contextlib 4 | import errno 5 | 6 | from systemd import login 7 | 8 | import pytest 9 | 10 | @contextlib.contextmanager 11 | def skip_oserror(code): 12 | try: 13 | yield 14 | except (OSError, IOError) as e: 15 | if e.errno == code: 16 | pytest.skip() 17 | raise 18 | 19 | def test_seats(): 20 | # just check that we get some sequence back 21 | with skip_oserror(errno.ENOENT): 22 | seats = login.seats() 23 | assert len(seats) >= 0 24 | 25 | def test_sessions(): 26 | with skip_oserror(errno.ENOENT): 27 | sessions = login.sessions() 28 | assert len(sessions) >= 0 29 | 30 | def test_machine_names(): 31 | with skip_oserror(errno.ENOENT): 32 | machine_names = login.machine_names() 33 | assert len(machine_names) >= 0 34 | 35 | def test_uids(): 36 | with skip_oserror(errno.ENOENT): 37 | uids = login.uids() 38 | assert len(uids) >= 0 39 | 40 | def test_monitor(): 41 | p = select.poll() 42 | 43 | with skip_oserror(errno.ENOENT): 44 | m = login.Monitor("machine") 45 | p.register(m, m.get_events()) 46 | login.machine_names() 47 | p.poll(1) 48 | login.machine_names() 49 | -------------------------------------------------------------------------------- /systemd/util.c: -------------------------------------------------------------------------------- 1 | /*** 2 | This file is part of systemd. 3 | 4 | Copyright 2010 Lennart Poettering 5 | 6 | systemd is free software; you can redistribute it and/or modify it 7 | under the terms of the GNU Lesser General Public License as published by 8 | the Free Software Foundation; either version 2.1 of the License, or 9 | (at your option) any later version. 10 | 11 | systemd is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public License 17 | along with systemd; If not, see . 18 | ***/ 19 | 20 | /* stuff imported from systemd without any changes */ 21 | 22 | #ifndef _GNU_SOURCE 23 | # define _GNU_SOURCE 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "util.h" 37 | 38 | int safe_atou(const char *s, unsigned *ret_u) { 39 | char *x = NULL; 40 | unsigned long l; 41 | 42 | assert(s); 43 | assert(ret_u); 44 | 45 | /* strtoul() is happy to parse negative values, and silently 46 | * converts them to unsigned values without generating an 47 | * error. We want a clean error, hence let's look for the "-" 48 | * prefix on our own, and generate an error. But let's do so 49 | * only after strtoul() validated that the string is clean 50 | * otherwise, so that we return EINVAL preferably over 51 | * ERANGE. */ 52 | 53 | errno = 0; 54 | l = strtoul(s, &x, 0); 55 | if (errno > 0) 56 | return -errno; 57 | if (!x || x == s || *x) 58 | return -EINVAL; 59 | if (s[0] == '-') 60 | return -ERANGE; 61 | if ((unsigned long) (unsigned) l != l) 62 | return -ERANGE; 63 | 64 | *ret_u = (unsigned) l; 65 | return 0; 66 | } 67 | 68 | static bool socket_ipv6_is_supported(void) { 69 | if (access("/proc/net/if_inet6", F_OK) != 0) 70 | return false; 71 | 72 | return true; 73 | } 74 | 75 | static int assign_address(const char *s, 76 | uint16_t port, 77 | union sockaddr_union *addr, unsigned *addr_len) { 78 | int r; 79 | 80 | /* IPv4 in w.x.y.z:p notation? */ 81 | r = inet_pton(AF_INET, s, &addr->in.sin_addr); 82 | if (r < 0) 83 | return -errno; 84 | 85 | if (r > 0) { 86 | /* Gotcha, it's a traditional IPv4 address */ 87 | addr->in.sin_family = AF_INET; 88 | addr->in.sin_port = htobe16(port); 89 | *addr_len = sizeof(struct sockaddr_in); 90 | } else { 91 | unsigned idx; 92 | 93 | if (strlen(s) > IF_NAMESIZE-1) 94 | return -EINVAL; 95 | 96 | /* Uh, our last resort, an interface name */ 97 | idx = if_nametoindex(s); 98 | if (idx == 0) 99 | return -EINVAL; 100 | 101 | addr->in6.sin6_family = AF_INET6; 102 | addr->in6.sin6_port = htobe16(port); 103 | addr->in6.sin6_scope_id = idx; 104 | addr->in6.sin6_addr = in6addr_any; 105 | *addr_len = sizeof(struct sockaddr_in6); 106 | } 107 | 108 | return 0; 109 | } 110 | 111 | 112 | int parse_sockaddr(const char *s, 113 | union sockaddr_union *addr, unsigned *addr_len) { 114 | 115 | char *e, *n; 116 | unsigned u; 117 | int r; 118 | 119 | if (*s == '[') { 120 | /* IPv6 in [x:.....:z]:p notation */ 121 | 122 | e = strchr(s+1, ']'); 123 | if (!e) 124 | return -EINVAL; 125 | 126 | n = strndupa(s+1, e-s-1); 127 | 128 | errno = 0; 129 | if (inet_pton(AF_INET6, n, &addr->in6.sin6_addr) <= 0) 130 | return errno > 0 ? -errno : -EINVAL; 131 | 132 | e++; 133 | if (*e) { 134 | if (*e != ':') 135 | return -EINVAL; 136 | 137 | e++; 138 | r = safe_atou(e, &u); 139 | if (r < 0) 140 | return r; 141 | 142 | if (u <= 0 || u > 0xFFFF) 143 | return -EINVAL; 144 | 145 | addr->in6.sin6_port = htobe16((uint16_t)u); 146 | } 147 | 148 | addr->in6.sin6_family = AF_INET6; 149 | *addr_len = sizeof(struct sockaddr_in6); 150 | 151 | } else { 152 | e = strchr(s, ':'); 153 | if (e) { 154 | r = safe_atou(e+1, &u); 155 | if (r < 0) 156 | return r; 157 | 158 | if (u <= 0 || u > 0xFFFF) 159 | return -EINVAL; 160 | 161 | n = strndupa(s, e-s); 162 | return assign_address(n, u, addr, addr_len); 163 | 164 | } else { 165 | r = safe_atou(s, &u); 166 | if (r < 0) 167 | return assign_address(s, 0, addr, addr_len); 168 | 169 | /* Just a port */ 170 | if (u <= 0 || u > 0xFFFF) 171 | return -EINVAL; 172 | 173 | if (socket_ipv6_is_supported()) { 174 | addr->in6.sin6_family = AF_INET6; 175 | addr->in6.sin6_port = htobe16((uint16_t)u); 176 | addr->in6.sin6_addr = in6addr_any; 177 | *addr_len = sizeof(struct sockaddr_in6); 178 | } else { 179 | addr->in.sin_family = AF_INET; 180 | addr->in.sin_port = htobe16((uint16_t)u); 181 | addr->in.sin_addr.s_addr = INADDR_ANY; 182 | *addr_len = sizeof(struct sockaddr_in); 183 | } 184 | } 185 | } 186 | 187 | return 0; 188 | } 189 | -------------------------------------------------------------------------------- /systemd/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /*** 4 | This file is part of systemd. 5 | 6 | Copyright 2010 Lennart Poettering 7 | 8 | systemd is free software; you can redistribute it and/or modify it 9 | under the terms of the GNU Lesser General Public License as published by 10 | the Free Software Foundation; either version 2.1 of the License, or 11 | (at your option) any later version. 12 | 13 | systemd is distributed in the hope that it will be useful, but 14 | WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | Lesser General Public License for more details. 17 | 18 | You should have received a copy of the GNU Lesser General Public License 19 | along with systemd; If not, see . 20 | ***/ 21 | 22 | #include 23 | #include 24 | 25 | union sockaddr_union { 26 | struct sockaddr sa; 27 | struct sockaddr_in in; 28 | struct sockaddr_in6 in6; 29 | }; 30 | 31 | int safe_atou(const char *s, unsigned *ret_u); 32 | int parse_sockaddr(const char *s, 33 | union sockaddr_union *addr, unsigned *addr_len); 34 | -------------------------------------------------------------------------------- /update-constants.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | for file in sys.argv[1:]: 4 | lines = iter(open(file).read().splitlines()) 5 | for line in lines: 6 | if line.startswith('#define SD_MESSAGE') and '_STR ' not in line: 7 | if line.endswith('\\'): 8 | line = line[:-1] + next(lines) 9 | print(' '.join(line.split())) 10 | --------------------------------------------------------------------------------