├── .github ├── FUNDING.yml └── workflows │ ├── packages.yml │ └── test.yml ├── .gitignore ├── .yamllint.yaml ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── pkg └── setproctitle │ ├── __init__.py │ └── py.typed ├── pyproject.toml ├── setup.py ├── src ├── c.h ├── darwin_set_process_name.c ├── darwin_set_process_name.h ├── setproctitle.c ├── spt.h ├── spt_config.h ├── spt_debug.c ├── spt_python.h ├── spt_setup.c ├── spt_setup.h ├── spt_status.c ├── spt_status.h └── spt_strlcpy.c ├── tests ├── __init__.py ├── conftest.py ├── module_test.py ├── pyrun.c ├── setproctitle_test.py ├── setthreadtitle_test.py └── test_win32.py ├── tools ├── Makefile ├── prctl_demo.c ├── pttest.c └── spt_demo.c └── tox.ini /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | github: 4 | - dvarrazzo 5 | custom: 6 | - "https://www.paypal.me/dvarrazzo" 7 | -------------------------------------------------------------------------------- /.github/workflows/packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Packages 3 | on: 4 | - workflow_dispatch 5 | 6 | jobs: 7 | 8 | build-sdist: 9 | name: Build sdist package 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: Checkout repos 13 | uses: actions/checkout@v4 14 | 15 | - name: Build sdist 16 | run: python setup.py sdist 17 | 18 | - name: Upload artifacts 19 | uses: actions/upload-artifact@v4 20 | with: 21 | name: sdist 22 | path: ./dist/* 23 | 24 | 25 | build-wheel: 26 | runs-on: ${{matrix.os}} 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | os: [ubuntu-24.04, windows-latest, macos-latest] 31 | pyver: [cp38, cp39, cp310, cp311, cp312, cp313, cp313t] 32 | 33 | steps: 34 | - name: Checkout repos 35 | uses: actions/checkout@v4 36 | 37 | - name: Build wheels 38 | uses: pypa/cibuildwheel@v2.23.3 39 | env: 40 | CIBW_BUILD: ${{matrix.pyver}}-* 41 | CIBW_ENABLE: cpython-freethreading 42 | CIBW_ARCHS_LINUX: auto 43 | CIBW_ARCHS_MACOS: auto universal2 44 | CIBW_ARCHS_WINDOWS: auto 45 | CIBW_TEST_EXTRAS: test 46 | CIBW_TEST_COMMAND: pytest --color=yes -m 'not embedded' {project}/tests 47 | # Passing a space in a param is a b*tch on windows. 48 | # However embedded tests are skipped anyway there. 49 | CIBW_TEST_COMMAND_WINDOWS: pytest --color=yes {project}/tests 50 | # musllinux tests fail with some pid mixup 51 | # cross-build macos images can't be tested on this runner. 52 | CIBW_TEST_SKIP: >- 53 | *-musllinux_* 54 | *-macosx_universal2:arm64 55 | pp*-macosx_* 56 | 57 | - name: Upload artifacts 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: wheel-${{ matrix.pyver }}-${{ matrix.os }} 61 | path: ./wheelhouse/*.whl 62 | 63 | 64 | build-cross-wheel: 65 | runs-on: ubuntu-24.04 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | pyver: [cp38, cp39, cp310, cp311, cp312, cp313, cp313t] 70 | arch: [aarch64, ppc64le] 71 | 72 | steps: 73 | - name: Checkout repos 74 | uses: actions/checkout@v4 75 | 76 | - name: Set up QEMU for multi-arch build 77 | uses: docker/setup-qemu-action@v3 78 | 79 | - name: Build wheels 80 | uses: pypa/cibuildwheel@v2.23.3 81 | env: 82 | CIBW_BUILD: ${{matrix.pyver}}-* 83 | CIBW_ENABLE: cpython-freethreading 84 | CIBW_ARCHS: ${{matrix.arch}} 85 | 86 | # Tests mostly fail because of some confusion with the python interpreter 87 | 88 | - name: Upload artifacts 89 | uses: actions/upload-artifact@v4 90 | with: 91 | name: wheel-${{ matrix.pyver }}-${{ matrix.arch }} 92 | path: ./wheelhouse/*.whl 93 | 94 | 95 | build-wheel-pypy: 96 | runs-on: ${{matrix.os}} 97 | strategy: 98 | fail-fast: false 99 | matrix: 100 | os: [ubuntu-24.04, windows-latest, macos-latest] 101 | 102 | steps: 103 | - name: Checkout repos 104 | uses: actions/checkout@v4 105 | 106 | - name: Build wheels 107 | uses: pypa/cibuildwheel@v2.21.2 108 | env: 109 | CIBW_BUILD: pp* 110 | CIBW_SKIP: pp37-* 111 | CIBW_TEST_EXTRAS: test 112 | CIBW_TEST_COMMAND: pytest --color=yes -m 'not embedded' {project}/tests 113 | # Passing a space in a param is a b*tch on windows. 114 | # However embedded tests are skipped anyway there. 115 | CIBW_TEST_COMMAND_WINDOWS: pytest --color=yes {project}/tests 116 | # musllinux tests fail with some pid mixup 117 | # cross-build macos images can't be tested on this runner. 118 | CIBW_TEST_SKIP: >- 119 | *-musllinux_* 120 | *-macosx_universal2:arm64 121 | pp*-macosx_* 122 | 123 | - name: Upload artifacts 124 | uses: actions/upload-artifact@v4 125 | with: 126 | name: wheels-pp-${{ matrix.os }} 127 | path: ./wheelhouse/*.whl 128 | 129 | 130 | build-cross-wheel-pypy: 131 | runs-on: ubuntu-24.04 132 | strategy: 133 | fail-fast: false 134 | 135 | steps: 136 | - name: Checkout repos 137 | uses: actions/checkout@v4 138 | 139 | - name: Set up QEMU for multi-arch build 140 | uses: docker/setup-qemu-action@v3 141 | 142 | - name: Build wheels 143 | uses: pypa/cibuildwheel@v2.21.2 144 | env: 145 | CIBW_BUILD: pp* 146 | CIBW_SKIP: pp37-* 147 | 148 | # Tests mostly fail because of some confusion with the python interpreter 149 | 150 | - name: Upload artifacts 151 | uses: actions/upload-artifact@v4 152 | with: 153 | name: wheels-pp-cross 154 | path: ./wheelhouse/*.whl 155 | 156 | 157 | merge: 158 | runs-on: ubuntu-latest 159 | needs: 160 | - build-sdist 161 | - build-wheel 162 | - build-cross-wheel 163 | - build-wheel-pypy 164 | - build-cross-wheel-pypy 165 | steps: 166 | - name: Merge Artifacts 167 | uses: actions/upload-artifact/merge@v4 168 | with: 169 | name: setproctitle-artifacts 170 | delete-merged: true 171 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tests 3 | on: 4 | push: 5 | # This should disable running the workflow on tags, according to the 6 | # on.. GitHub Actions docs. 7 | branches: 8 | - "*" 9 | pull_request: 10 | 11 | jobs: 12 | 13 | linux-tests: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: 19 | - "3.8" 20 | - "3.9" 21 | - "3.10" 22 | - "3.11" 23 | - "3.12" 24 | - "3.13" 25 | - "3.13t" 26 | - "pypy-3.8" 27 | - "pypy-3.9" 28 | - "pypy-3.10" 29 | os: 30 | - ubuntu-24.04 31 | 32 | steps: 33 | - name: Checkout repos 34 | uses: actions/checkout@v4 35 | 36 | - name: Set up Python 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: ${{matrix.python-version}} 40 | allow-prereleases: true 41 | architecture: x64 42 | 43 | - name: Install Tox 44 | run: pip install tox 45 | 46 | - name: Run tests 47 | run: tox -e ${{matrix.python-version}} -- -vrsx --color=yes 48 | 49 | 50 | windows-tests: 51 | runs-on: windows-latest 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | python-version: 56 | - 3.8 57 | - 3.9 58 | - "3.10" 59 | - "3.11" 60 | - "3.12" 61 | - "3.13" 62 | - "3.13t" 63 | architecture: ['x64', 'x86'] 64 | 65 | steps: 66 | - name: Checkout repos 67 | uses: actions/checkout@v4 68 | 69 | - name: Set up Python ${{matrix.python-version}} 70 | uses: actions/setup-python@v5 71 | with: 72 | python-version: ${{matrix.python-version}} 73 | allow-prereleases: true 74 | architecture: ${{matrix.architecture}} 75 | 76 | - name: Install Tox 77 | run: pip install tox 78 | 79 | - name: Run tests 80 | run: tox -e ${{matrix.python-version}} -- -vrsx --color=yes 81 | 82 | 83 | macos-tests: 84 | runs-on: ${{ matrix.os }} 85 | strategy: 86 | fail-fast: false 87 | matrix: 88 | python-version: 89 | - "3.11" 90 | - "3.12" 91 | - "3.13" 92 | - "3.13t" 93 | os: 94 | - macos-latest 95 | include: 96 | - python-version: 3.8 97 | os: macos-13 98 | - python-version: 3.9 99 | os: macos-13 100 | - python-version: "3.10" 101 | os: macos-13 102 | 103 | steps: 104 | - name: Checkout repos 105 | uses: actions/checkout@v4 106 | 107 | - name: Set up Python ${{matrix.python-version}} 108 | uses: actions/setup-python@v5 109 | with: 110 | python-version: ${{matrix.python-version}} 111 | allow-prereleases: true 112 | 113 | - name: Install Tox 114 | run: pip install tox 115 | 116 | - name: Run tests 117 | run: tox -e ${{matrix.python-version}} -- -vrsx --color=yes 118 | 119 | 120 | macos-native-python-test: 121 | runs-on: macos-latest 122 | 123 | steps: 124 | - name: Checkout repos 125 | uses: actions/checkout@v4 126 | 127 | - name: Install Tox 128 | run: xcrun python3 -m pip install tox 129 | 130 | - name: Run tests 131 | run: | 132 | export XCODE_PYTHON=`xcrun python3 -c "import sys;print(sys.executable)"` 133 | $XCODE_PYTHON -c "import sys; print(sys.version)" 134 | $XCODE_PYTHON -m tox -e xcode -- -vrsx --color=yes 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.sw[op] 3 | *.orig 4 | *.pyc 5 | *.rej 6 | *.html 7 | tools/pttest 8 | tools/sptdemo 9 | tools/prctl_demo 10 | tools/ps_status.[ch] 11 | build 12 | dist 13 | py3 14 | tests/pyrun?.? 15 | env 16 | /setproctitle.egg-info 17 | _setproctitle.*.so 18 | /.tox 19 | /packages 20 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | 5 | rules: 6 | truthy: 7 | check-keys: false 8 | line-length: 9 | max: 85 10 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | Releases history 2 | ---------------- 3 | 4 | Version 1.3.6 5 | ------------- 6 | 7 | - Add support for free-threading (issue #147) 8 | 9 | 10 | Version 1.3.5 11 | ------------- 12 | 13 | - Fix bouncing Dock icon on macOS (issue #143). 14 | - Fix building on C23 compilers (issue #145). 15 | 16 | 17 | Version 1.3.4 18 | ------------- 19 | 20 | - Add support for Python 3.13 (issue #139). 21 | - Drop support for Python 3.7. 22 | 23 | 24 | Version 1.3.3 25 | ------------- 26 | 27 | - Add support for Python 3.12. 28 | - Fix package metadata to include Python 3.11, 3.12. 29 | 30 | 31 | Version 1.3.2 32 | ------------- 33 | 34 | - Restore import-time initialization of macOS to avoid crash on thread+fork 35 | (issue #113). 36 | 37 | 38 | Version 1.3.1 39 | ------------- 40 | 41 | - Fixed segfault on macOS 12.5 in forked processes (issue #111). 42 | Note that, as a workaround, Activity Monitor will show the title of the 43 | parent. 44 | 45 | 46 | Version 1.3.0 47 | ------------- 48 | 49 | - Added fallback no-op implementation if building the extension fails. 50 | - Added support for displaying title as the process name in MacOS Activity 51 | Monitor (issue #10). 52 | - Fixed "Symbol not found: _Py_GetArgcArgv" error when using Xcode provided 53 | Python (issues #82, #103). 54 | - Fixed FreeBSD support, broken in 1.2 (issue #94). 55 | - Added package type annotations (issue #101). 56 | - Dropped support for Python 3.6. 57 | 58 | 59 | Version 1.2.3 60 | ------------- 61 | 62 | - Added Python 3.10 packages (issue #102). 63 | - Added Wheel packages for macOS (issue #96). 64 | - Package build moved to cibuildwheel, other wheels provided (issue #47). 65 | 66 | 67 | Version 1.2.2 68 | ------------- 69 | 70 | - Fixed Windows build (issues #89, #90). 71 | - Added wheel packages for Windows (issues #47, #90). 72 | - Added wheel packages for aarch64 (issue #95). 73 | 74 | 75 | Version 1.2.1 76 | ------------- 77 | 78 | - Fixed segfault after ``os.environ.clear()`` (issue #88). 79 | 80 | 81 | Version 1.2 82 | ~~~~~~~~~~~ 83 | 84 | - added ``getthreadtitle()`` and ``setthreadtitle()``. 85 | - Initialisation of the module moved to the first usage: importing the module 86 | doesn't cause side effects. 87 | - Manage much longer command lines (issue #52) 88 | - Improved build on BSD, dropped ancient versions (issue #67). 89 | - Fixed build for Python 3.8 (issues #66, #72) 90 | - Added support for Python 3.9 91 | - Dropped support for Python < 3.6 92 | 93 | 94 | Version 1.1.10 95 | ~~~~~~~~~~~~~~ 96 | 97 | - Fixed building with certain ``prctl.h`` implementations (issue #44). 98 | - Use ``setuptools`` if available (issue #48). 99 | 100 | 101 | Version 1.1.9 102 | ~~~~~~~~~~~~~ 103 | 104 | - Fixed build on VC (issues #20, #33). 105 | - Added ``MANIFEST.in`` to the source distribution to help with RPM building 106 | (issue #30). 107 | 108 | 109 | Version 1.1.8 110 | ~~~~~~~~~~~~~ 111 | 112 | - Added support for Python "diehard" 2.4 (pull request #3). 113 | - Fixed build on Mac OS X 10.9 Maverick (issue #27). 114 | 115 | 116 | Version 1.1.7 117 | ~~~~~~~~~~~~~ 118 | 119 | - Added PyPy support, courtesy of Ozan Turksever - http://www.logsign.net 120 | (pull request #2). 121 | 122 | 123 | Version 1.1.6 124 | ~~~~~~~~~~~~~ 125 | 126 | - The module can be compiled again on Windows (issue #21). 127 | 128 | 129 | Version 1.1.5 130 | ~~~~~~~~~~~~~ 131 | 132 | - No module bug, but a packaging issue: files ``README`` and ``HISTORY`` 133 | added back into the distribution. 134 | 135 | 136 | Version 1.1.4 137 | ~~~~~~~~~~~~~ 138 | 139 | - The module works correctly in embedded Python. 140 | - ``setproctitle()`` accepts a keyword argument. 141 | - Debug output support always compiled in: the variable ``SPT_DEBUG`` can be 142 | used to emit debug log. 143 | 144 | 145 | Version 1.1.3 146 | ~~~~~~~~~~~~~ 147 | 148 | - Don't clobber environ if the variable ``SPT_NOENV`` is set (issue #16). 149 | 150 | 151 | Version 1.1.2 152 | ~~~~~~~~~~~~~ 153 | 154 | - Find the setproctitle include file on OpenBSD (issue #11). 155 | - Skip test with unicode if the file system encoding wouldn't make it pass 156 | (issue #13). 157 | 158 | 159 | Version 1.1.1 160 | ~~~~~~~~~~~~~ 161 | 162 | - Fixed segfault when the module is imported under mod_wsgi (issue #9). 163 | 164 | 165 | Version 1.1 166 | ~~~~~~~~~~~ 167 | 168 | - The module works correctly with Python 3. 169 | 170 | 171 | Version 1.0.1 172 | ~~~~~~~~~~~~~ 173 | 174 | - ``setproctitle()`` works even when Python messes up with argv, e.g. when run 175 | with the -m option (issue #8). 176 | 177 | 178 | Version 1.0 179 | ~~~~~~~~~~~ 180 | 181 | No major change since the previous version. The module has been heavily used 182 | in production environment without any problem reported, so it's time to declare 183 | it stable. 184 | 185 | 186 | Version 0.4 187 | ~~~~~~~~~~~ 188 | 189 | - Module works on BSD (tested on FreeBSD 7.2). 190 | 191 | - Module works on Windows. Many thanks to `Develer`_ for providing a neat `GCC 192 | package for Windows with Python integration`__ that made the Windows porting 193 | painless. 194 | 195 | .. _Develer: http://www.develer.com/ 196 | .. __: http://www.develer.com/oss/GccWinBinaries 197 | 198 | 199 | Version 0.3 200 | ~~~~~~~~~~~ 201 | 202 | - Module works on Mac OS X 10.2. Reported working on OS X 10.6 too. 203 | 204 | 205 | Version 0.2 206 | ~~~~~~~~~~~ 207 | 208 | - Added ``prctl()`` call on Linux >= 2.6.9 to update ``/proc/self/status``. 209 | 210 | 211 | Version 0.1 212 | ~~~~~~~~~~~ 213 | 214 | - Initial public release. 215 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2009, Daniele Varrazzo 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include HISTORY.rst 2 | include README.rst 3 | include COPYRIGHT 4 | include MANIFEST.in 5 | include Makefile 6 | include src/*.c 7 | include src/*.h 8 | include tests/*.py 9 | include tests/*.c 10 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | A Python module to customize the process title 2 | ============================================== 3 | 4 | .. image:: https://github.com/dvarrazzo/py-setproctitle/workflows/Tests/badge.svg 5 | :target: https://github.com/dvarrazzo/py-setproctitle/actions?query=workflow%3ATests 6 | :alt: Tests 7 | 8 | :author: Daniele Varrazzo 9 | 10 | The ``setproctitle`` module allows a process to change its title (as displayed 11 | by system tools such as ``ps``, ``top`` or MacOS Activity Monitor). 12 | 13 | Changing the title is mostly useful in multi-process systems, for example 14 | when a master process is forked: changing the children's title allows to 15 | identify the task each process is busy with. The technique is used by 16 | PostgreSQL_ and the `OpenSSH Server`_ for example. 17 | 18 | The procedure is hardly portable across different systems. PostgreSQL provides 19 | a good `multi-platform implementation`__: this package was born as a wrapper 20 | around PostgreSQL code. 21 | 22 | - `Homepage `__ 23 | - `Download `__ 24 | - `Bug tracker `__ 25 | 26 | 27 | .. _PostgreSQL: http://www.postgresql.org 28 | .. _OpenSSH Server: http://www.openssh.com/ 29 | .. __: http://doxygen.postgresql.org/ps__status_8c_source.html 30 | 31 | 32 | Installation 33 | ------------ 34 | 35 | ``setproctitle`` is a C extension: in order to build it you will need a C 36 | compiler and the Python development support (the ``python-dev`` or 37 | ``python3-dev`` package in most Linux distributions). No further external 38 | dependencies are required. 39 | 40 | You can use ``pip`` to install the module:: 41 | 42 | pip install setproctitle 43 | 44 | You can use ``pip -t`` or ``virtualenv`` for local installations, ``sudo pip`` 45 | for a system-wide one... the usual stuff. Read pip_ or virtualenv_ docs for 46 | all the details. 47 | 48 | .. _pip: https://pip.readthedocs.org/ 49 | .. _virtualenv: https://virtualenv.readthedocs.org/ 50 | 51 | 52 | Usage 53 | ----- 54 | 55 | .. note:: 56 | You should import and use the module (even just calling ``getproctitle()``) 57 | pretty early in your program lifetime: code writing env vars `may 58 | interfere`__ with the module initialisation. 59 | 60 | .. __: https://github.com/dvarrazzo/py-setproctitle/issues/42 61 | 62 | 63 | The ``setproctitle`` module exports the following functions: 64 | 65 | ``setproctitle(title)`` 66 | Set *title* as the title for the current process. 67 | 68 | ``getproctitle()`` 69 | Return the current process title. 70 | 71 | The process title is usually visible in files such as ``/proc/PID/cmdline``, 72 | ``/proc/PID/status``, ``/proc/PID/comm``, depending on the operating system 73 | and kernel version. These information are used by user-space tools such as 74 | ``ps`` and ``top``. 75 | 76 | 77 | ``setthreadtitle(title)`` 78 | Set *title* as the title for the current thread. 79 | 80 | ``getthreadtitle()`` 81 | Get the current thread title. 82 | 83 | The thread title is exposed by some operating systems as the file 84 | ``/proc/PID/task/TID/comm``, which is used by certain tools such as ``htop``. 85 | 86 | 87 | Environment variables 88 | ~~~~~~~~~~~~~~~~~~~~~ 89 | 90 | A few environment variables can be used to customize the module behavior: 91 | 92 | ``SPT_NOENV`` 93 | Avoid clobbering ``/proc/PID/environ``. 94 | 95 | On many platforms, setting the process title will clobber the 96 | ``environ`` memory area. ``os.environ`` will work as expected from within 97 | the Python process, but the content of the file ``/proc/PID/environ`` will 98 | be overwritten. If you require this file not to be broken you can set the 99 | ``SPT_NOENV`` environment variable to any non-empty value: in this case 100 | the maximum length for the title will be limited to the length of the 101 | command line. 102 | 103 | ``SPT_DEBUG`` 104 | Print debug information on ``stderr``. 105 | 106 | If the module doesn't work as expected you can set this variable to a 107 | non-empty value to generate information useful for debugging. Note that 108 | the most useful information is printed when the module is imported, not 109 | when the functions are called. 110 | 111 | 112 | Module status 113 | ------------- 114 | 115 | The module can be currently compiled and effectively used on the following 116 | platforms: 117 | 118 | - GNU/Linux 119 | - BSD 120 | - MacOS X 121 | - Windows 122 | 123 | Note that on Windows there is no way to change the process string: 124 | what the module does is to create a *Named Object* whose value can be read 125 | using a tool such as `Process Explorer`_ (contribution of a more useful tool 126 | to be used together with ``setproctitle`` would be well accepted). 127 | 128 | The module can probably work on HP-UX, but I haven't found any to test with. 129 | It is unlikely that it can work on Solaris instead. 130 | 131 | .. _Process Explorer: http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx 132 | -------------------------------------------------------------------------------- /pkg/setproctitle/__init__.py: -------------------------------------------------------------------------------- 1 | """Allow customization of the process title.""" 2 | 3 | import os 4 | import sys 5 | import logging 6 | 7 | logger = logging.getLogger("setproctitle") 8 | 9 | __version__ = "1.3.6" 10 | 11 | __all__ = [ 12 | "setproctitle", 13 | "getproctitle", 14 | "setthreadtitle", 15 | "getthreadtitle", 16 | ] 17 | 18 | 19 | def setproctitle(title: str) -> None: 20 | logger.debug("setproctitle C module not available") 21 | return None 22 | 23 | 24 | def getproctitle() -> str: 25 | logger.debug("setproctitle C module not available") 26 | return " ".join(sys.argv) 27 | 28 | 29 | def setthreadtitle(title: str) -> None: 30 | logger.debug("setproctitle C module not available") 31 | return None 32 | 33 | 34 | def getthreadtitle() -> str: 35 | logger.debug("setproctitle C module not available") 36 | return "" 37 | 38 | 39 | try: 40 | from . import _setproctitle # type: ignore 41 | except ImportError as e: 42 | # Emulate SPT_DEBUG showing process info in the C module. 43 | if os.environ.get("SPT_DEBUG", ""): 44 | logging.basicConfig() 45 | logger.setLevel(logging.DEBUG) 46 | logger.debug("failed to import setproctitle: %s", e) 47 | else: 48 | setproctitle = _setproctitle.setproctitle # noqa: F811 49 | getproctitle = _setproctitle.getproctitle # noqa: F811 50 | setthreadtitle = _setproctitle.setthreadtitle # noqa: F811 51 | getthreadtitle = _setproctitle.getthreadtitle # noqa: F811 52 | 53 | 54 | # Call getproctitle to initialize structures and avoid problems caused 55 | # by fork() on macOS (see #113). 56 | if sys.platform == "darwin": 57 | getproctitle() 58 | -------------------------------------------------------------------------------- /pkg/setproctitle/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrazzo/py-setproctitle/11d5ba71f0dc2bee2deb32ad17ac577101d404cd/pkg/setproctitle/py.typed -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 79 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | setproctitle setup script. 4 | 5 | Copyright (c) 2009 Daniele Varrazzo 6 | """ 7 | 8 | import re 9 | import sys 10 | 11 | from setuptools import setup, Extension, find_packages 12 | from setuptools.command.build_ext import build_ext 13 | 14 | with open("pkg/setproctitle/__init__.py") as f: 15 | data = f.read() 16 | m = re.search(r"""(?m)^__version__\s*=\s*['"]([^'"]+)['"]""", data) 17 | if not m: 18 | raise Exception(f"cannot find version in {f.name}") 19 | VERSION = m.group(1) 20 | 21 | define_macros = {} 22 | define_macros["SPT_VERSION"] = VERSION 23 | 24 | platform_sources = [] 25 | 26 | if sys.platform.startswith("linux"): 27 | define_macros["HAVE_SYS_PRCTL_H"] = 1 28 | 29 | elif sys.platform == "darwin": 30 | # __darwin__ symbol is not defined; __APPLE__ is instead. 31 | define_macros["__darwin__"] = 1 32 | platform_sources.append("src/darwin_set_process_name.c") 33 | 34 | elif "bsd" in sys.platform: # OMG, how many of them are? 35 | define_macros["BSD"] = 1 36 | define_macros["HAVE_SETPROCTITLE"] = 1 37 | define_macros["HAVE_PS_STRING"] = 1 38 | 39 | # NOTE: the module may work on HP-UX using pstat 40 | # thus setting define_macros['HAVE_SYS_PSTAT_H'] 41 | # see http://www.noc.utoronto.ca/~mikep/unix/HPTRICKS 42 | # But I have none handy to test with. 43 | 44 | mod_spt = Extension( 45 | "setproctitle._setproctitle", 46 | define_macros=list(define_macros.items()), 47 | sources=[ 48 | "src/setproctitle.c", 49 | "src/spt_debug.c", 50 | "src/spt_setup.c", 51 | "src/spt_status.c", 52 | "src/spt_strlcpy.c", 53 | ] 54 | + platform_sources, 55 | ) 56 | 57 | # Try to include the long description in the setup 58 | kwargs = {} 59 | try: 60 | kwargs["long_description"] = ( 61 | open("README.rst").read() + "\n" + open("HISTORY.rst").read() 62 | ) 63 | kwargs["long_description_content_type"] = "text/x-rst" 64 | except Exception: 65 | pass 66 | 67 | classifiers = """\ 68 | Development Status :: 5 - Production/Stable 69 | Intended Audience :: Developers 70 | License :: OSI Approved :: BSD License 71 | Programming Language :: C 72 | Programming Language :: Python :: 3 73 | Programming Language :: Python :: 3.8 74 | Programming Language :: Python :: 3.9 75 | Programming Language :: Python :: 3.10 76 | Programming Language :: Python :: 3.11 77 | Programming Language :: Python :: 3.12 78 | Programming Language :: Python :: 3.13 79 | Operating System :: POSIX :: Linux 80 | Operating System :: POSIX :: BSD 81 | Operating System :: MacOS :: MacOS X 82 | Operating System :: Microsoft :: Windows 83 | Topic :: Software Development 84 | """.splitlines() 85 | 86 | 87 | class BuildError(Exception): 88 | pass 89 | 90 | 91 | class setproctitle_build_ext(build_ext): 92 | def run(self) -> None: 93 | try: 94 | super().run() 95 | except Exception as e: 96 | print(f"Failed to build C module: {e}", file=sys.stderr) 97 | raise BuildError(str(e)) 98 | 99 | def build_extension(self, ext): 100 | try: 101 | super().build_extension(ext) 102 | except Exception as e: 103 | print( 104 | f"Failed to build extension {ext.name}: {e}", file=sys.stderr 105 | ) 106 | raise BuildError(str(e)) 107 | 108 | 109 | def do_build(with_extension): 110 | ext_modules = [mod_spt] if with_extension else [] 111 | setup( 112 | name="setproctitle", 113 | description="A Python module to customize the process title", 114 | version=VERSION, 115 | author="Daniele Varrazzo", 116 | author_email="daniele.varrazzo@gmail.com", 117 | url="https://github.com/dvarrazzo/py-setproctitle", 118 | download_url="http://pypi.python.org/pypi/setproctitle/", 119 | license="BSD-3-Clause", 120 | platforms=["GNU/Linux", "BSD", "MacOS X", "Windows"], 121 | python_requires=">=3.8", 122 | classifiers=classifiers, 123 | packages=["setproctitle"], 124 | package_dir={"setproctitle": "pkg/setproctitle"}, 125 | ext_modules=ext_modules, 126 | package_data={"setproctitle": ["py.typed"]}, 127 | extras_require={"test": ["pytest"]}, 128 | cmdclass={"build_ext": setproctitle_build_ext}, 129 | zip_safe=False, 130 | **kwargs, 131 | ) 132 | 133 | 134 | try: 135 | do_build(with_extension=True) 136 | except BuildError: 137 | do_build(with_extension=False) 138 | -------------------------------------------------------------------------------- /src/c.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * c.h 4 | * A few fundamental C definitions. 5 | * 6 | * Copyright (c) 2009 Daniele Varrazzo 7 | *------------------------------------------------------------------------- 8 | */ 9 | 10 | #ifndef C_H 11 | #define C_H 12 | 13 | #include "spt_config.h" 14 | 15 | #if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ <= 201710L) 16 | #include 17 | #endif 18 | 19 | #include 20 | 21 | /* Let's use our version of strlcpy to avoid portability problems */ 22 | size_t spt_strlcpy(char *dst, const char *src, size_t siz); 23 | 24 | /* VC defines _WIN32, not WIN32 */ 25 | #ifdef _WIN32 26 | #ifndef WIN32 27 | #define WIN32 _WIN32 28 | #endif 29 | #endif 30 | 31 | #ifdef WIN32 32 | #include 33 | #endif 34 | 35 | #endif /* C_H */ 36 | -------------------------------------------------------------------------------- /src/darwin_set_process_name.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Set process title in a way compatible with Activity Monitor and other 4 | MacOS system tools. 5 | 6 | Idea is borrowed from libuv (used by node.js) 7 | See https://github.com/libuv/libuv/blob/v1.x/src/unix/darwin-proctitle.c 8 | Implementation rewritten from scratch, fixing various libuv bugs among other things 9 | 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include "darwin_set_process_name.h" 18 | 19 | #define DONE_IF(cond) if (cond) goto done; 20 | 21 | /* Undocumented Launch Services functions */ 22 | typedef enum { 23 | kLSDefaultSessionID = -2, 24 | } LSSessionID; 25 | CFTypeRef LSGetCurrentApplicationASN(void); 26 | OSStatus LSSetApplicationInformationItem(LSSessionID, CFTypeRef, CFStringRef, CFStringRef, CFDictionaryRef*); 27 | CFDictionaryRef LSApplicationCheckIn(LSSessionID, CFDictionaryRef); 28 | void LSSetApplicationLaunchServicesServerConnectionStatus(uint64_t, void *); 29 | 30 | typedef struct { 31 | void * application_services_handle; 32 | 33 | CFBundleRef launch_services_bundle; 34 | typeof(LSGetCurrentApplicationASN) * pLSGetCurrentApplicationASN; 35 | typeof(LSSetApplicationInformationItem) * pLSSetApplicationInformationItem; 36 | typeof(LSApplicationCheckIn) * pLSApplicationCheckIn; 37 | typeof(LSSetApplicationLaunchServicesServerConnectionStatus) * pLSSetApplicationLaunchServicesServerConnectionStatus; 38 | 39 | CFStringRef * display_name_key_ptr; 40 | 41 | } launch_services_t; 42 | 43 | static bool launch_services_init(launch_services_t * it) { 44 | enum { 45 | has_nothing, 46 | has_application_services_handle 47 | } state = has_nothing; 48 | bool ret = false; 49 | 50 | it->application_services_handle = dlopen("/System/Library/Frameworks/" 51 | "ApplicationServices.framework/" 52 | "Versions/Current/ApplicationServices", 53 | RTLD_LAZY | RTLD_LOCAL); 54 | DONE_IF(!it->application_services_handle); 55 | ++state; 56 | 57 | it->launch_services_bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); 58 | DONE_IF(!it->launch_services_bundle); 59 | 60 | #define LOAD_METHOD(name) \ 61 | *(void **)(&it->p ## name ) = \ 62 | CFBundleGetFunctionPointerForName(it->launch_services_bundle, CFSTR("_" #name)); \ 63 | DONE_IF(!it->p ## name); 64 | 65 | LOAD_METHOD(LSGetCurrentApplicationASN) 66 | LOAD_METHOD(LSSetApplicationInformationItem) 67 | LOAD_METHOD(LSApplicationCheckIn) 68 | LOAD_METHOD(LSSetApplicationLaunchServicesServerConnectionStatus) 69 | 70 | #undef LOAD_METHOD 71 | 72 | it->display_name_key_ptr = 73 | CFBundleGetDataPointerForName(it->launch_services_bundle, CFSTR("_kLSDisplayNameKey")); 74 | DONE_IF(!it->display_name_key_ptr || !*it->display_name_key_ptr); 75 | 76 | ret = true; 77 | 78 | done: 79 | switch(state) { 80 | case has_application_services_handle: if (!ret) dlclose(it->application_services_handle); 81 | case has_nothing: ; 82 | } 83 | return ret; 84 | } 85 | 86 | static inline void launch_services_destroy(launch_services_t * it) { 87 | dlclose(it->application_services_handle); 88 | } 89 | 90 | static bool launch_services_set_process_title(const launch_services_t * it, const char * title) { 91 | 92 | enum { 93 | has_nothing, 94 | has_cf_title 95 | } state = has_nothing; 96 | bool ret = false; 97 | 98 | static bool checked_in = false; 99 | 100 | CFTypeRef asn; 101 | CFStringRef cf_title; 102 | CFDictionaryRef info_dict; 103 | CFMutableDictionaryRef mutable_info_dict; 104 | CFStringRef LSUIElement_key; 105 | 106 | if (!checked_in) { 107 | it->pLSSetApplicationLaunchServicesServerConnectionStatus(0, NULL); 108 | 109 | // See https://github.com/dvarrazzo/py-setproctitle/issues/143 110 | // We need to set LSUIElement (https://developer.apple.com/documentation/bundleresources/information-property-list/lsuielement) 111 | // key to true to avoid macOS > 15 displaying the Dock icon. 112 | info_dict = CFBundleGetInfoDictionary(CFBundleGetMainBundle()); 113 | mutable_info_dict = CFDictionaryCreateMutableCopy(NULL, 0, info_dict); 114 | LSUIElement_key = CFStringCreateWithCString(NULL, "LSUIElement", kCFStringEncodingUTF8); 115 | CFDictionaryAddValue(mutable_info_dict, LSUIElement_key, kCFBooleanTrue); 116 | CFRelease(LSUIElement_key); 117 | 118 | it->pLSApplicationCheckIn(kLSDefaultSessionID, mutable_info_dict); 119 | CFRelease(mutable_info_dict); 120 | 121 | checked_in = true; 122 | } 123 | 124 | asn = it->pLSGetCurrentApplicationASN(); 125 | DONE_IF(!asn); 126 | 127 | cf_title = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8); 128 | DONE_IF(!cf_title); 129 | ++state; 130 | DONE_IF(it->pLSSetApplicationInformationItem(kLSDefaultSessionID, 131 | asn, 132 | *it->display_name_key_ptr, 133 | cf_title, 134 | NULL) != noErr); 135 | ret = true; 136 | done: 137 | switch(state) { 138 | case has_cf_title: CFRelease(cf_title); 139 | case has_nothing: ; 140 | } 141 | 142 | return ret; 143 | } 144 | 145 | static bool darwin_pthread_setname_np(const char* name) { 146 | char namebuf[64]; /* MAXTHREADNAMESIZE according to libuv */ 147 | 148 | strncpy(namebuf, name, sizeof(namebuf) - 1); 149 | namebuf[sizeof(namebuf) - 1] = '\0'; 150 | 151 | return (pthread_setname_np(namebuf) != 0); 152 | } 153 | 154 | 155 | bool darwin_set_process_title(const char * title) { 156 | 157 | enum { 158 | has_nothing, 159 | has_launch_services 160 | } state = has_nothing; 161 | bool ret = false; 162 | 163 | launch_services_t launch_services; 164 | 165 | DONE_IF(!launch_services_init(&launch_services)); 166 | ++state; 167 | 168 | DONE_IF(!launch_services_set_process_title(&launch_services, title)); 169 | 170 | (void)darwin_pthread_setname_np(title); 171 | 172 | ret = true; 173 | 174 | done: 175 | switch(state) { 176 | case has_launch_services: launch_services_destroy(&launch_services); 177 | case has_nothing: ; 178 | } 179 | 180 | return ret; 181 | } 182 | -------------------------------------------------------------------------------- /src/darwin_set_process_name.h: -------------------------------------------------------------------------------- 1 | #ifndef HEADER_DARWIN_SET_PROCESS_NAME_H_INCLUDED 2 | #define HEADER_DARWIN_SET_PROCESS_NAME_H_INCLUDED 3 | 4 | #include "spt_config.h" 5 | 6 | #include 7 | 8 | HIDDEN bool darwin_set_process_title(const char * title); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/setproctitle.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * setproctitle.c 4 | * Python extension module to update and read the process title. 5 | * 6 | * Copyright (c) 2009 Daniele Varrazzo 7 | * 8 | * The module allows Python code to access the functions get_ps_display() 9 | * and set_ps_display(). 10 | * 11 | *------------------------------------------------------------------------- 12 | */ 13 | 14 | #include "spt.h" 15 | #include "spt_setup.h" 16 | #include "spt_status.h" 17 | 18 | #ifndef SPT_VERSION 19 | #define SPT_VERSION unknown 20 | #endif 21 | 22 | /* macro trick to stringify a macro expansion */ 23 | #define xstr(s) str(s) 24 | #define str(s) #s 25 | 26 | /* ----------------------------------------------------- */ 27 | 28 | static char spt_setproctitle__doc__[] = 29 | "setproctitle(title) -- Change the process title." 30 | ; 31 | 32 | static PyObject * 33 | spt_setproctitle(PyObject *self, PyObject *args, PyObject *kwargs) 34 | { 35 | const char *title = NULL; 36 | static char *kwlist[] = {"title", NULL}; 37 | 38 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &title)) { 39 | spt_debug("failed to parse tuple and keywords"); 40 | return NULL; 41 | } 42 | 43 | if (spt_setup() < 0) { 44 | spt_debug("failed to initialize setproctitle"); 45 | } 46 | 47 | /* Initialize the process title */ 48 | set_ps_display(title, true); 49 | 50 | Py_RETURN_NONE; 51 | } 52 | 53 | 54 | static char spt_getproctitle__doc__[] = 55 | "getproctitle() -- Get the current process title." 56 | ; 57 | 58 | static PyObject * 59 | spt_getproctitle(PyObject *self, PyObject *args) 60 | { 61 | size_t tlen; 62 | const char *title; 63 | 64 | if (spt_setup() < 0) { 65 | spt_debug("failed to initialize setproctitle"); 66 | } 67 | 68 | title = get_ps_display(&tlen); 69 | 70 | return Py_BuildValue("s#", title, (int)tlen); 71 | } 72 | 73 | 74 | static char spt_setthreadtitle__doc__[] = 75 | "setthreadtitle(title) -- Change the thread title." 76 | ; 77 | 78 | static PyObject * 79 | spt_setthreadtitle(PyObject *self, PyObject *args, PyObject *kwargs) 80 | { 81 | const char *title = NULL; 82 | static char *kwlist[] = {"title", NULL}; 83 | 84 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &title)) { 85 | spt_debug("failed to parse tuple and keywords"); 86 | return NULL; 87 | } 88 | 89 | set_thread_title(title); 90 | 91 | Py_RETURN_NONE; 92 | } 93 | 94 | 95 | static char spt_getthreadtitle__doc__[] = 96 | "getthreadtitle() -- Return the thread title." 97 | ; 98 | 99 | static PyObject * 100 | spt_getthreadtitle(PyObject *self, PyObject *args) 101 | { 102 | char title[16] = {'\0'}; 103 | 104 | get_thread_title(title); 105 | 106 | return Py_BuildValue("s", title); 107 | } 108 | 109 | /* Module initialization function */ 110 | 111 | static int 112 | spt_exec(PyObject *m) 113 | { 114 | spt_debug("module init"); 115 | return 0; 116 | } 117 | 118 | /* List of slots defined in the module */ 119 | 120 | static PyModuleDef_Slot spt_slots[] = { 121 | {Py_mod_exec, spt_exec}, 122 | #if PY_VERSION_HEX >= 0x030c0000 123 | {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, 124 | #endif 125 | #if PY_VERSION_HEX >= 0x030d0000 126 | {Py_mod_gil, Py_MOD_GIL_NOT_USED}, 127 | #endif 128 | {0, NULL} 129 | }; 130 | 131 | /* List of methods defined in the module */ 132 | 133 | static struct PyMethodDef spt_methods[] = { 134 | {"setproctitle", 135 | (PyCFunction)spt_setproctitle, 136 | METH_VARARGS|METH_KEYWORDS, 137 | spt_setproctitle__doc__}, 138 | 139 | {"getproctitle", 140 | (PyCFunction)spt_getproctitle, 141 | METH_NOARGS, 142 | spt_getproctitle__doc__}, 143 | 144 | {"setthreadtitle", 145 | (PyCFunction)spt_setthreadtitle, 146 | METH_VARARGS|METH_KEYWORDS, 147 | spt_setthreadtitle__doc__}, 148 | 149 | {"getthreadtitle", 150 | (PyCFunction)spt_getthreadtitle, 151 | METH_NOARGS, 152 | spt_getthreadtitle__doc__}, 153 | 154 | {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ 155 | }; 156 | 157 | 158 | /* Initialization function for the module (*must* be called initsetproctitle) */ 159 | 160 | static char setproctitle_module_documentation[] = 161 | "Allow customization of the process title." 162 | ; 163 | 164 | static struct PyModuleDef moduledef = { 165 | PyModuleDef_HEAD_INIT, 166 | "_setproctitle", 167 | setproctitle_module_documentation, 168 | 0, 169 | spt_methods, 170 | spt_slots, 171 | NULL, 172 | NULL, 173 | NULL 174 | }; 175 | 176 | PyMODINIT_FUNC 177 | PyInit__setproctitle(void) 178 | { 179 | return PyModuleDef_Init(&moduledef); 180 | } 181 | -------------------------------------------------------------------------------- /src/spt.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * spt.h 4 | * Definitions useful throughout all the extension. 5 | * 6 | * Copyright (c) 2010 Daniele Varrazzo 7 | * 8 | *------------------------------------------------------------------------- 9 | */ 10 | 11 | #ifndef SPT_H 12 | #define SPT_H 13 | 14 | #include "spt_config.h" 15 | #include "spt_python.h" 16 | 17 | /* expose the debug function to the extension code */ 18 | HIDDEN void spt_debug(const char *fmt, ...); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/spt_config.h: -------------------------------------------------------------------------------- 1 | /* Stub file: should be created in configuration phase */ 2 | /* This configuration was taken from an Ubuntu i386 installation. */ 3 | 4 | /* Define to 1 if you have the `setproctitle' function. */ 5 | /* #undef HAVE_SETPROCTITLE */ 6 | 7 | /* Define to 1 if the PS_STRINGS thing exists. */ 8 | /* #undef HAVE_PS_STRINGS */ 9 | 10 | /* Define to 1 if you have the header file. */ 11 | /* #undef HAVE_SYS_PSTAT_H */ 12 | 13 | /* Define to 1 if you have the header file. */ 14 | /* #undef HAVE_SYS_PRCTL_H */ 15 | 16 | /* GCC 4.0 and later have support for specifying symbol visibility */ 17 | #if __GNUC__ >= 4 && !defined(__MINGW32__) 18 | # define HIDDEN __attribute__((visibility("hidden"))) 19 | #else 20 | # define HIDDEN 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /src/spt_debug.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * spt_python.c 4 | * A simple function for the module debugging. 5 | * 6 | * Copyright (c) 2009 Daniele Varrazzo 7 | * 8 | * Debug logging is enabled if the environment variable SPT_DEBUG is set to a 9 | * non-empty value at runtime. 10 | * 11 | *------------------------------------------------------------------------- 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "spt_config.h" 19 | 20 | HIDDEN void 21 | spt_debug(const char *fmt, ...) 22 | { 23 | static int enabled = -1; 24 | va_list ap; 25 | 26 | /* check if debug is enabled */ 27 | if (-1 == enabled) { 28 | char *d = getenv("SPT_DEBUG"); 29 | enabled = (d && *d) ? 1 : 0; 30 | } 31 | 32 | /* bail out if debug is not enabled */ 33 | if (0 == enabled) { return; } 34 | 35 | fprintf(stderr, "[SPT]: "); 36 | va_start(ap, fmt); 37 | vfprintf(stderr, fmt, ap); 38 | va_end(ap); 39 | fprintf(stderr, "\n"); 40 | } 41 | -------------------------------------------------------------------------------- /src/spt_python.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * spt_python.h 4 | * Include and customize Python definitions. 5 | * 6 | * Copyright (c) 2010 Daniele Varrazzo 7 | * 8 | *------------------------------------------------------------------------- 9 | */ 10 | 11 | #ifndef SPT_PYTHON_H 12 | #define SPT_PYTHON_H 13 | 14 | #define PY_SSIZE_T_CLEAN 15 | #include 16 | 17 | /* Detect pypy */ 18 | #ifdef PYPY_VERSION 19 | #define IS_PYPY 20 | #endif 21 | 22 | #ifndef __darwin__ 23 | /* defined in Modules/main.c but not publically declared */ 24 | void Py_GetArgcArgv(int *argc, wchar_t ***argv); 25 | #endif 26 | 27 | #endif /* SPT_PYTHON_H */ 28 | -------------------------------------------------------------------------------- /src/spt_setup.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * spt_setup.c 4 | * Initalization code for the spt_status.c module functions. 5 | * 6 | * Copyright (c) 2009 Daniele Varrazzo 7 | * 8 | *------------------------------------------------------------------------- 9 | */ 10 | 11 | #include "spt_setup.h" 12 | 13 | #include "spt.h" 14 | #include "spt_status.h" 15 | 16 | #include 17 | 18 | /* Darwin doesn't export environ */ 19 | #if defined(__darwin__) 20 | #include 21 | #define environ (*_NSGetEnviron()) 22 | #elif !defined(_WIN32) 23 | extern char **environ; 24 | #endif 25 | 26 | #ifndef WIN32 27 | 28 | /* Return a concatenated version of a strings vector. 29 | * 30 | * Return newly allocated heap space: clean it up with free(). 31 | * 32 | * Return NULL and raise an exception on error. 33 | */ 34 | static char * 35 | join_argv(int argc, char **argv) 36 | { 37 | int i; 38 | size_t len = 0; 39 | char *buf; 40 | char *src; 41 | char *dest; 42 | 43 | /* Calculate the final string length */ 44 | for (i = 0; i < argc; i++) { 45 | len += strlen(argv[i]) + 1; 46 | } 47 | 48 | if (!(dest = buf = (char *)malloc(len))) { 49 | PyErr_NoMemory(); 50 | return NULL; 51 | } 52 | 53 | /* Copy the strings in the buffer joining with spaces */ 54 | for (i = 0; i < argc; i++) { 55 | src = argv[i]; 56 | while (*src) { 57 | *dest++ = *src++; 58 | } 59 | *dest++ = ' '; 60 | } 61 | *--dest = '\x00'; 62 | 63 | return buf; 64 | } 65 | 66 | #ifndef __darwin__ 67 | 68 | /* I don't expect it to be defined: should include limits.h. But then it's 69 | * another of those ./configure can of worms to find where it is... */ 70 | #ifndef ARG_MAX 71 | #define ARG_MAX (96 * 1024) 72 | #endif 73 | 74 | 75 | /* Return a copy of argv[0] encoded in the default encoding. 76 | * 77 | * Return a newly allocated buffer to be released with free(). 78 | * 79 | * Return NULL in case of error. If the error shouldn't be ignored, also set 80 | * a Python exception. 81 | */ 82 | static char * 83 | get_encoded_arg0(wchar_t *argv0) 84 | { 85 | PyObject *ua = NULL, *ba = NULL; 86 | char *rv = NULL; 87 | 88 | if (!(ua = PyUnicode_FromWideChar(argv0, -1))) { 89 | spt_debug("failed to convert argv[0] to unicode"); 90 | PyErr_Clear(); 91 | goto exit; 92 | } 93 | 94 | if (!(ba = PyUnicode_AsEncodedString( 95 | ua, PyUnicode_GetDefaultEncoding(), "strict"))) { 96 | spt_debug("failed to encode argv[0]"); 97 | PyErr_Clear(); 98 | goto exit; 99 | } 100 | 101 | if (!(rv = strdup(PyBytes_AsString(ba)))) { 102 | PyErr_NoMemory(); 103 | } 104 | 105 | exit: 106 | Py_XDECREF(ua); 107 | Py_XDECREF(ba); 108 | 109 | return rv; 110 | } 111 | 112 | 113 | /* Find the original arg buffer starting from the env position. 114 | * 115 | * Return a malloc'd argv vector, pointing to the original arguments. 116 | * 117 | * Return NULL in case of error. If the error shouldn't be ignored, also set 118 | * a Python exception. 119 | * 120 | * Required on Python 3 as Py_GetArgcArgv doesn't return pointers to the 121 | * original area. It can be used on Python 2 too in case we can't get argv, 122 | * such as in embedded environment. 123 | */ 124 | static char ** 125 | find_argv_from_env(int argc, char *arg0) 126 | { 127 | int i; 128 | char **buf = NULL; 129 | char **rv = NULL; 130 | char *ptr; 131 | char *limit; 132 | 133 | spt_debug("walking from environ to look for the arguments"); 134 | 135 | if (!(buf = (char **)malloc((argc + 1) * sizeof(char *)))) { 136 | spt_debug("can't malloc %d args!", argc); 137 | PyErr_NoMemory(); 138 | goto exit; 139 | } 140 | buf[argc] = NULL; 141 | 142 | /* Walk back from environ until you find argc-1 null-terminated strings. 143 | * Don't look for argv[0] as it's probably not preceded by 0. */ 144 | ptr = environ[0]; 145 | if (!ptr) { 146 | /* It happens on os.environ.clear() */ 147 | spt_debug("environ pointer is NULL"); 148 | goto exit; 149 | } 150 | spt_debug("found environ at %p", ptr); 151 | limit = ptr - ARG_MAX; 152 | --ptr; 153 | for (i = argc - 1; i >= 1; --i) { 154 | if (*ptr) { 155 | spt_debug("zero %d not found", i); 156 | goto exit; 157 | } 158 | --ptr; 159 | while (*ptr && ptr > limit) { --ptr; } 160 | if (ptr <= limit) { 161 | spt_debug("failed to found arg %d start", i); 162 | goto exit; 163 | } 164 | buf[i] = (ptr + 1); 165 | spt_debug("found argv[%d] at %p: %s", i, buf[i], buf[i]); 166 | } 167 | 168 | /* The first arg has not a zero in front. But what we have is reliable 169 | * enough (modulo its encoding). Check if it is exactly what found. 170 | * 171 | * The check is known to fail on OS X with locale C if there are 172 | * non-ascii characters in the executable path. See Python issue #9167 173 | */ 174 | ptr -= strlen(arg0); 175 | spt_debug("argv[0] should be at %p", ptr); 176 | 177 | if (ptr <= limit) { 178 | spt_debug("failed to find argv[0] start"); 179 | goto exit; 180 | } 181 | if (strcmp(ptr, arg0)) { 182 | spt_debug("argv[0] '%s' doesn't match '%s'", ptr, arg0); 183 | goto exit; 184 | } 185 | 186 | /* We have all the pieces of the jigsaw. */ 187 | buf[0] = ptr; 188 | spt_debug("found argv[0]: %s", buf[0]); 189 | rv = buf; 190 | buf = NULL; 191 | 192 | exit: 193 | if (buf) { free(buf); } 194 | 195 | return rv; 196 | } 197 | 198 | 199 | /* Come on, why is this missing?! this is just cruel! 200 | * I guess you club seal pups for hobby. */ 201 | PyObject * 202 | PyFile_FromString(const char *filename, const char *mode) 203 | { 204 | PyObject *io = NULL; 205 | PyObject *rv = NULL; 206 | 207 | if (!(io = PyImport_ImportModule("io"))) { 208 | spt_debug("failed to import io"); 209 | goto exit; 210 | } 211 | 212 | rv = PyObject_CallMethod(io, "open", "ss", filename, mode); 213 | 214 | exit: 215 | Py_XDECREF(io); 216 | return rv; 217 | } 218 | 219 | /* Read the number of arguments and the first argument from /proc/pid/cmdline 220 | * 221 | * Return 0 if found, else -1. Return arg0 in a malloc'd array. 222 | * 223 | * If the function fails in a way that shouldn't be ignored, also set 224 | * a Python exception. 225 | */ 226 | static int 227 | get_args_from_proc(int *argc_o, char **arg0_o) 228 | { 229 | /* allow /proc/PID/cmdline, with oversize max_pid, and them some. */ 230 | #define FNLEN 30 231 | char fn[FNLEN]; 232 | 233 | PyObject *os = NULL; 234 | PyObject *pid_py = NULL; 235 | long pid; 236 | PyObject *f = NULL; 237 | PyObject *cl = NULL; 238 | 239 | PyObject *tmp = NULL; 240 | int rv = -1; 241 | 242 | spt_debug("looking for args into proc fs"); 243 | 244 | /* get the pid from os.getpid() */ 245 | if (!(os = PyImport_ImportModule("os"))) { 246 | spt_debug("failed to import os"); 247 | goto exit; 248 | } 249 | if (!(pid_py = PyObject_CallMethod(os, "getpid", NULL))) { 250 | spt_debug("calling os.getpid() failed"); 251 | /* os.getpid() may be not available, so ignore this error. */ 252 | PyErr_Clear(); 253 | goto exit; 254 | } 255 | if (-1 == (pid = PyLong_AsLong(pid_py))) { 256 | spt_debug("os.getpid() returned crap?"); 257 | /* Don't bother to check PyErr_Occurred as pid can't just be -1. */ 258 | goto exit; 259 | } 260 | 261 | /* get the content of /proc/PID/cmdline */ 262 | snprintf(fn, FNLEN, "/proc/%ld/cmdline", pid); 263 | if (!(f = PyFile_FromString(fn, "rb"))) { 264 | spt_debug("opening '%s' failed", fn); 265 | /* That's ok: procfs is easily not available on menomated unices */ 266 | PyErr_Clear(); 267 | goto exit; 268 | } 269 | /* the file has been open in binary mode, so we get bytes */ 270 | cl = PyObject_CallMethod(f, "read", NULL); 271 | if (!(tmp = PyObject_CallMethod(f, "close", NULL))) { 272 | spt_debug("closing failed"); 273 | } 274 | else { 275 | Py_DECREF(tmp); 276 | } 277 | 278 | if (!cl) { 279 | spt_debug("reading failed"); 280 | /* could there be some protected environment where a process cannot 281 | * read its own pid? Who knows, better not to risk. */ 282 | PyErr_Clear(); 283 | goto exit; 284 | } 285 | 286 | /* the cmdline is a buffer of null-terminated strings. We can strdup it to 287 | * get a copy of arg0, and count the zeros to get argc */ 288 | { 289 | char *ccl; 290 | Py_ssize_t i; 291 | 292 | if (!(ccl = PyBytes_AsString(cl))) { 293 | spt_debug("failed to get cmdline string"); 294 | goto exit; 295 | } 296 | if (!(*arg0_o = strdup(ccl))) { 297 | spt_debug("arg0 strdup failed"); 298 | PyErr_NoMemory(); 299 | goto exit; 300 | } 301 | spt_debug("got argv[0] = '%s' from /proc", *arg0_o); 302 | 303 | *argc_o = 0; 304 | for (i = PyBytes_Size(cl) - 1; i >= 0; --i) { 305 | if (ccl[i] == '\0') { (*argc_o)++; } 306 | } 307 | spt_debug("got argc = %d from /proc", *argc_o); 308 | } 309 | 310 | /* success */ 311 | rv = 0; 312 | 313 | exit: 314 | Py_XDECREF(cl); 315 | Py_XDECREF(f); 316 | Py_XDECREF(pid_py); 317 | Py_XDECREF(os); 318 | 319 | return rv; 320 | } 321 | 322 | /* Find the original arg buffer, return 0 if found, else -1. 323 | * 324 | * If found, set argc to the number of arguments, argv to an array 325 | * of pointers to the single arguments. The array is allocated via malloc. 326 | * 327 | * If the function fails in a way that shouldn't be ignored, also set 328 | * a Python exception. 329 | * 330 | * The function overcomes three Py_GetArgcArgv shortcomings: 331 | * - some python parameters mess up with the original argv, e.g. -m 332 | * (see issue #8) 333 | * - with Python 3, argv is a decoded copy and doesn't point to 334 | * the original area. 335 | * - If python is embedded, the function doesn't return anything. 336 | */ 337 | static int 338 | get_argc_argv(int *argc_o, char ***argv_o) 339 | { 340 | int argc = 0; 341 | wchar_t **argv_py = NULL; 342 | char **argv = NULL; 343 | char *arg0 = NULL; 344 | int rv = -1; 345 | 346 | #ifndef IS_PYPY 347 | spt_debug("reading argc/argv from Python main"); 348 | Py_GetArgcArgv(&argc, &argv_py); 349 | #endif 350 | 351 | if (argc > 0) { 352 | spt_debug("found %d arguments", argc); 353 | 354 | if (!(arg0 = get_encoded_arg0(argv_py[0]))) { 355 | spt_debug("couldn't get a copy of argv[0]"); 356 | goto exit; 357 | } 358 | /* we got argv: on py2 it used to pointsto the right place in memory; on 359 | * py3 we only got a copy of argv[0]: we will use it to look from env 360 | */ 361 | } 362 | else { 363 | spt_debug("no good news from Py_GetArgcArgv"); 364 | 365 | /* get a copy of argv[0] from /proc, so we get back in the same 366 | * situation of Py3 */ 367 | if (0 > get_args_from_proc(&argc, &arg0)) { 368 | spt_debug("failed to get args from proc fs"); 369 | goto exit; 370 | } 371 | } 372 | 373 | /* If we don't know argv but we know the content of argv[0], we can walk 374 | * backwards from environ and see if we get it. */ 375 | if (arg0 && !argv) { 376 | if (!(argv = find_argv_from_env(argc, arg0))) { 377 | spt_debug("couldn't find argv from environ"); 378 | goto exit; 379 | } 380 | } 381 | 382 | /* success */ 383 | *argc_o = argc; 384 | *argv_o = argv; 385 | argv = NULL; 386 | rv = 0; 387 | 388 | exit: 389 | if (arg0) { free(arg0); } 390 | if (argv) { free(argv); } 391 | 392 | return rv; 393 | } 394 | 395 | #else /* __darwin__ */ 396 | 397 | static int 398 | get_argc_argv(int *argc_o, char ***argv_o) 399 | { 400 | int * pargc = _NSGetArgc(); 401 | if (!pargc) { 402 | spt_debug("_NSGetArgc returned NULL"); 403 | return -1; 404 | } 405 | int argc = *pargc; 406 | char *** pargv = _NSGetArgv(); 407 | if (!pargv) { 408 | spt_debug("_NSGetArgv returned NULL"); 409 | return -1; 410 | } 411 | char ** buf = malloc((argc + 1) * sizeof(char *)); 412 | if (!buf) { 413 | spt_debug("can't malloc %d args!", argc); 414 | PyErr_NoMemory(); 415 | return -1; 416 | } 417 | memcpy(buf, *pargv, argc * sizeof(char *)); 418 | buf[argc] = NULL; 419 | *argc_o = argc; 420 | *argv_o = buf; 421 | 422 | return 0; 423 | } 424 | 425 | #endif /* __darwin__ */ 426 | 427 | #endif /* !WIN32 */ 428 | 429 | 430 | /* Initialize the module internal functions. 431 | * 432 | * The function reproduces the initialization performed by PostgreSQL 433 | * to be able to call the functions in pg_status.c 434 | * 435 | * Return 0 in case of success, else -1. In case of failure with an error that 436 | * shouldn't be ignored, also set a Python exception. 437 | * 438 | * The function should be called only once in the process lifetime. 439 | * so is called at module initialization. After the function is called, 440 | * set_ps_display() can be used. 441 | */ 442 | int 443 | spt_setup(void) 444 | { 445 | const int not_happened = 3; 446 | static int rv = 3; 447 | 448 | /* Make sure setup happens just once, either successful or failed */ 449 | if (rv != not_happened) { 450 | spt_debug("setup was called more than once!"); 451 | return rv; 452 | } 453 | 454 | rv = -1; 455 | 456 | #ifndef WIN32 457 | int argc = 0; 458 | char **argv = NULL; 459 | char *init_title; 460 | 461 | if (0 > get_argc_argv(&argc, &argv)) { 462 | spt_debug("get_argc_argv failed"); 463 | goto exit; 464 | } 465 | 466 | save_ps_display_args(argc, argv); 467 | 468 | /* Set up the first title to fully initialize the code */ 469 | if (!(init_title = join_argv(argc, argv))) { goto exit; } 470 | init_ps_display(init_title); 471 | free(init_title); 472 | 473 | #else 474 | /* On Windows save_ps_display_args is a no-op 475 | * This is a good news, because Py_GetArgcArgv seems not usable. 476 | */ 477 | LPTSTR init_title = GetCommandLine(); 478 | init_ps_display(init_title); 479 | #endif 480 | 481 | rv = 0; 482 | 483 | exit: 484 | return rv; 485 | } 486 | 487 | -------------------------------------------------------------------------------- /src/spt_setup.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * spt_setup.h 4 | * Initalization code for the spt_status.c module functions. 5 | * 6 | * Copyright (c) 2009 Daniele Varrazzo 7 | * 8 | *------------------------------------------------------------------------- 9 | */ 10 | 11 | #ifndef SPT_SETUP_H 12 | #define SPT_SETUP_H 13 | 14 | #include "spt_config.h" 15 | 16 | HIDDEN int spt_setup(void); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/spt_status.c: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------- 2 | * spt_status.c 3 | * 4 | * Routines to support changing the ps display of a process. 5 | * Mechanism differs wildly across platforms. 6 | * 7 | * Copyright (c) 2000-2009, PostgreSQL Global Development Group 8 | * Copyright (c) 2009 Daniele Varrazzo 9 | * various details abducted from various places 10 | * 11 | * This file was taken from PostgreSQL. The PostgreSQL copyright terms follow. 12 | *-------------------------------------------------------------------- 13 | */ 14 | 15 | /* 16 | * PostgreSQL Database Management System 17 | * (formerly known as Postgres, then as Postgres95) 18 | * 19 | * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group 20 | * 21 | * Portions Copyright (c) 1994, The Regents of the University of California 22 | * 23 | * Permission to use, copy, modify, and distribute this software and its 24 | * documentation for any purpose, without fee, and without a written agreement 25 | * is hereby granted, provided that the above copyright notice and this 26 | * paragraph and the following two paragraphs appear in all copies. 27 | * 28 | * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR 29 | * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING 30 | * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS 31 | * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE 32 | * POSSIBILITY OF SUCH DAMAGE. 33 | * 34 | * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 35 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 36 | * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 37 | * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO 38 | * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 39 | */ 40 | 41 | #include "spt.h" 42 | #include "spt_config.h" 43 | 44 | /* note: VC doesn't have this, but it was working on mingw instead 45 | * so check on _WIN32 (defined by VC) instead of WIN32 */ 46 | #ifndef _WIN32 47 | #include 48 | #endif 49 | 50 | #ifdef HAVE_SYS_PSTAT_H 51 | #include /* for HP-UX */ 52 | #endif 53 | #ifdef HAVE_PS_STRINGS 54 | #include /* for old BSD */ 55 | #include 56 | #endif 57 | #ifdef HAVE_SYS_PRCTL_H 58 | #include /* for Linux >= 2.6.9 */ 59 | #endif 60 | #if defined(__darwin__) 61 | #include 62 | #include "darwin_set_process_name.h" 63 | #endif 64 | 65 | #include "spt_status.h" 66 | 67 | #include 68 | #include 69 | #include 70 | 71 | /* Darwin doesn't export environ */ 72 | #if defined(__darwin__) 73 | #define environ (*_NSGetEnviron()) 74 | #else 75 | extern char **environ; 76 | #endif 77 | 78 | bool update_process_title = true; 79 | 80 | /* 81 | * Alternative ways of updating ps display: 82 | * 83 | * PS_USE_SETPROCTITLE 84 | * use the function setproctitle(const char *, ...) 85 | * (newer BSD systems) 86 | * PS_USE_PSTAT 87 | * use the pstat(PSTAT_SETCMD, ) 88 | * (HPUX) 89 | * PS_USE_PS_STRINGS 90 | * assign PS_STRINGS->ps_argvstr = "string" 91 | * (some BSD systems) 92 | * PS_USE_CHANGE_ARGV 93 | * assign argv[0] = "string" 94 | * (some other BSD systems) 95 | * PS_USE_PRCTL 96 | * use prctl(PR_SET_NAME, ) 97 | * (Linux >= 2.6.9) 98 | * PS_USE_CLOBBER_ARGV 99 | * write over the argv and environment area 100 | * (most SysV-like systems) 101 | * PS_USE_WIN32 102 | * push the string out as the name of a Windows event 103 | * PS_USE_NONE 104 | * don't update ps display 105 | * (This is the default, as it is safest.) 106 | */ 107 | #if defined(HAVE_SETPROCTITLE) 108 | #define PS_USE_SETPROCTITLE 109 | #elif defined(HAVE_PSTAT) && defined(PSTAT_SETCMD) 110 | #define PS_USE_PSTAT 111 | #elif defined(HAVE_PS_STRINGS) 112 | #define PS_USE_PS_STRINGS 113 | #elif (defined(BSD) || defined(__bsdi__) || defined(__hurd__)) && !defined(__darwin__) 114 | #define PS_USE_CHANGE_ARGV 115 | #elif defined(__linux__) || defined(_AIX) || defined(__sgi) || (defined(sun) && !defined(BSD)) || defined(ultrix) || defined(__ksr__) || defined(__osf__) || defined(__svr4__) || defined(__svr5__) 116 | #define PS_USE_CLOBBER_ARGV 117 | #elif defined(__darwin__) 118 | #define PS_USE_CLOBBER_ARGV 119 | #define PS_USE_DARWIN 120 | #elif defined(WIN32) 121 | #define PS_USE_WIN32 122 | #else 123 | #define PS_USE_NONE 124 | #endif 125 | 126 | /* we use this strategy together with another one (probably PS_USE_CLOBBER_ARGV) */ 127 | #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_NAME) && !defined(PS_USE_NONE) 128 | #define PS_USE_PRCTL 129 | #endif 130 | 131 | /* Different systems want the buffer padded differently */ 132 | #if defined(_AIX) || defined(__linux__) || defined(__svr4__) || defined(__darwin__) 133 | #define PS_PADDING '\0' 134 | #else 135 | #define PS_PADDING ' ' 136 | #endif 137 | 138 | 139 | #ifndef PS_USE_CLOBBER_ARGV 140 | /* all but one options need a buffer to write their ps line in */ 141 | #define PS_BUFFER_SIZE 256 142 | static char ps_buffer[PS_BUFFER_SIZE]; 143 | static const size_t ps_buffer_size = PS_BUFFER_SIZE; 144 | #else /* PS_USE_CLOBBER_ARGV */ 145 | static char *ps_buffer; /* will point to argv area */ 146 | static size_t ps_buffer_size; /* space determined at run time */ 147 | static size_t last_status_len; /* use to minimize length of clobber */ 148 | #endif /* PS_USE_CLOBBER_ARGV */ 149 | 150 | static size_t ps_buffer_fixed_size; /* size of the constant prefix */ 151 | 152 | /* save the original argv[] location here */ 153 | static int save_argc; 154 | static char **save_argv; 155 | 156 | 157 | /* 158 | * Call this early in startup to save the original argc/argv values. 159 | * If needed, we make a copy of the original argv[] array to preserve it 160 | * from being clobbered by subsequent ps_display actions. 161 | * 162 | * (The original argv[] will not be overwritten by this routine, but may be 163 | * overwritten during init_ps_display. Also, the physical location of the 164 | * environment strings may be moved, so this should be called before any code 165 | * that might try to hang onto a getenv() result.) 166 | */ 167 | char ** 168 | save_ps_display_args(int argc, char **argv) 169 | { 170 | save_argc = argc; 171 | save_argv = argv; 172 | 173 | #if defined(PS_USE_CLOBBER_ARGV) 174 | 175 | /* 176 | * If we're going to overwrite the argv area, count the available space. 177 | * Also move the environment to make additional room. 178 | */ 179 | { 180 | char *end_of_area = NULL; 181 | char **new_environ; 182 | int i; 183 | 184 | /* 185 | * check for contiguous argv strings 186 | */ 187 | for (i = 0; i < argc; i++) 188 | { 189 | if (i == 0 || end_of_area + 1 == argv[i]) 190 | end_of_area = argv[i] + strlen(argv[i]); 191 | } 192 | 193 | if (end_of_area == NULL) /* probably can't happen? */ 194 | { 195 | ps_buffer = NULL; 196 | ps_buffer_size = 0; 197 | return argv; 198 | } 199 | 200 | { 201 | /* 202 | * Clobbering environ works fine from within the process, but some 203 | * external utils use /proc/PID/environ and they would find noting, 204 | * or mess, if we clobber it. A user can define SPT_NOENV to limit 205 | * clobbering to argv (see ticket #16). 206 | */ 207 | char *noenv; 208 | 209 | noenv = getenv("SPT_NOENV"); 210 | if (!noenv || !*noenv) { 211 | 212 | /* 213 | * check for contiguous environ strings following argv 214 | */ 215 | for (i = 0; environ[i] != NULL; i++) 216 | { 217 | if (end_of_area + 1 == environ[i]) 218 | end_of_area = environ[i] + strlen(environ[i]); 219 | } 220 | 221 | /* 222 | * move the environment out of the way 223 | */ 224 | spt_debug("environ has been copied"); 225 | new_environ = (char **) malloc((i + 1) * sizeof(char *)); 226 | for (i = 0; environ[i] != NULL; i++) 227 | new_environ[i] = strdup(environ[i]); 228 | new_environ[i] = NULL; 229 | environ = new_environ; 230 | } 231 | } 232 | 233 | ps_buffer = argv[0]; 234 | last_status_len = ps_buffer_size = end_of_area - argv[0]; 235 | 236 | } 237 | #endif /* PS_USE_CLOBBER_ARGV */ 238 | 239 | #if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV) 240 | 241 | /* 242 | * If we're going to change the original argv[] then make a copy for 243 | * argument parsing purposes. 244 | * 245 | * (NB: do NOT think to remove the copying of argv[], even though 246 | * postmaster.c finishes looking at argv[] long before we ever consider 247 | * changing the ps display. On some platforms, getopt() keeps pointers 248 | * into the argv array, and will get horribly confused when it is 249 | * re-called to analyze a subprocess' argument string if the argv storage 250 | * has been clobbered meanwhile. Other platforms have other dependencies 251 | * on argv[]. 252 | */ 253 | { 254 | char **new_argv; 255 | int i; 256 | 257 | new_argv = (char **) malloc((argc + 1) * sizeof(char *)); 258 | for (i = 0; i < argc; i++) 259 | new_argv[i] = strdup(argv[i]); 260 | new_argv[argc] = NULL; 261 | 262 | #if defined(__darwin__) 263 | 264 | /* 265 | * Darwin (and perhaps other NeXT-derived platforms?) has a static 266 | * copy of the argv pointer, which we may fix like so: 267 | */ 268 | *_NSGetArgv() = new_argv; 269 | #endif 270 | 271 | argv = new_argv; 272 | } 273 | #endif /* PS_USE_CHANGE_ARGV or PS_USE_CLOBBER_ARGV */ 274 | 275 | return argv; 276 | } 277 | 278 | /* 279 | * Call this once during subprocess startup to set the identification 280 | * values. At this point, the original argv[] array may be overwritten. 281 | */ 282 | void 283 | init_ps_display(const char *initial_str) 284 | { 285 | 286 | #ifndef PS_USE_NONE 287 | /* no ps display if you didn't call save_ps_display_args() */ 288 | if (!save_argv) 289 | return; 290 | #ifdef PS_USE_CLOBBER_ARGV 291 | /* If ps_buffer is a pointer, it might still be null */ 292 | if (!ps_buffer) 293 | return; 294 | #endif 295 | 296 | /* 297 | * Overwrite argv[] to point at appropriate space, if needed 298 | */ 299 | 300 | #ifdef PS_USE_CHANGE_ARGV 301 | save_argv[0] = ps_buffer; 302 | save_argv[1] = NULL; 303 | #endif /* PS_USE_CHANGE_ARGV */ 304 | 305 | #ifdef PS_USE_CLOBBER_ARGV 306 | { 307 | int i; 308 | 309 | /* make extra argv slots point at end_of_area (a NUL) */ 310 | for (i = 1; i < save_argc; i++) 311 | save_argv[i] = ps_buffer + ps_buffer_size; 312 | } 313 | #endif /* PS_USE_CLOBBER_ARGV */ 314 | 315 | /* 316 | * Make fixed prefix of ps display. 317 | */ 318 | 319 | ps_buffer[0] = '\0'; 320 | 321 | ps_buffer_fixed_size = strlen(ps_buffer); 322 | 323 | set_ps_display(initial_str, true); 324 | #endif /* not PS_USE_NONE */ 325 | } 326 | 327 | 328 | 329 | /* 330 | * Call this to update the ps status display to a fixed prefix plus an 331 | * indication of what you're currently doing passed in the argument. 332 | */ 333 | void 334 | set_ps_display(const char *activity, bool force) 335 | { 336 | 337 | if (!force && !update_process_title) 338 | return; 339 | 340 | #ifndef PS_USE_NONE 341 | 342 | #ifdef PS_USE_CLOBBER_ARGV 343 | /* If ps_buffer is a pointer, it might still be null */ 344 | if (!ps_buffer) 345 | return; 346 | #endif 347 | 348 | /* Update ps_buffer to contain both fixed part and activity */ 349 | spt_strlcpy(ps_buffer + ps_buffer_fixed_size, activity, 350 | ps_buffer_size - ps_buffer_fixed_size); 351 | 352 | /* Transmit new setting to kernel, if necessary */ 353 | 354 | #ifdef PS_USE_DARWIN 355 | darwin_set_process_title(ps_buffer); 356 | #endif 357 | 358 | #ifdef PS_USE_SETPROCTITLE 359 | setproctitle("%s", ps_buffer); 360 | #endif 361 | 362 | #ifdef PS_USE_PSTAT 363 | { 364 | union pstun pst; 365 | 366 | pst.pst_command = ps_buffer; 367 | pstat(PSTAT_SETCMD, pst, strlen(ps_buffer), 0, 0); 368 | } 369 | #endif /* PS_USE_PSTAT */ 370 | 371 | #ifdef PS_USE_PS_STRINGS 372 | PS_STRINGS->ps_nargvstr = 1; 373 | PS_STRINGS->ps_argvstr = ps_buffer; 374 | #endif /* PS_USE_PS_STRINGS */ 375 | 376 | #ifdef PS_USE_CLOBBER_ARGV 377 | { 378 | size_t buflen; 379 | 380 | /* pad unused memory */ 381 | buflen = strlen(ps_buffer); 382 | /* clobber remainder of old status string */ 383 | if (last_status_len > buflen) 384 | memset(ps_buffer + buflen, PS_PADDING, last_status_len - buflen); 385 | last_status_len = buflen; 386 | } 387 | #endif /* PS_USE_CLOBBER_ARGV */ 388 | 389 | #ifdef PS_USE_PRCTL 390 | prctl(PR_SET_NAME, ps_buffer); 391 | #endif 392 | 393 | #ifdef PS_USE_WIN32 394 | { 395 | /* 396 | * Win32 does not support showing any changed arguments. To make it at 397 | * all possible to track which backend is doing what, we create a 398 | * named object that can be viewed with for example Process Explorer. 399 | */ 400 | static HANDLE ident_handle = INVALID_HANDLE_VALUE; 401 | char name[PS_BUFFER_SIZE + 32]; 402 | 403 | if (ident_handle != INVALID_HANDLE_VALUE) 404 | CloseHandle(ident_handle); 405 | 406 | sprintf(name, "python(%d): %s", _getpid(), ps_buffer); 407 | 408 | ident_handle = CreateEvent(NULL, TRUE, FALSE, name); 409 | } 410 | #endif /* PS_USE_WIN32 */ 411 | #endif /* not PS_USE_NONE */ 412 | } 413 | 414 | 415 | /* 416 | * Returns what's currently in the ps display, in case someone needs 417 | * it. Note that only the activity part is returned. On some platforms 418 | * the string will not be null-terminated, so return the effective 419 | * length into *displen. 420 | */ 421 | const char * 422 | get_ps_display(size_t *displen) 423 | { 424 | #ifdef PS_USE_CLOBBER_ARGV 425 | size_t offset; 426 | 427 | /* If ps_buffer is a pointer, it might still be null */ 428 | if (!ps_buffer) 429 | { 430 | *displen = 0; 431 | return ""; 432 | } 433 | 434 | /* Remove any trailing spaces to offset the effect of PS_PADDING */ 435 | offset = ps_buffer_size; 436 | while (offset > ps_buffer_fixed_size && ps_buffer[offset - 1] == PS_PADDING) 437 | offset--; 438 | 439 | *displen = offset - ps_buffer_fixed_size; 440 | #else 441 | *displen = strlen(ps_buffer + ps_buffer_fixed_size); 442 | #endif 443 | 444 | return ps_buffer + ps_buffer_fixed_size; 445 | } 446 | 447 | 448 | void 449 | set_thread_title(const char *title) 450 | { 451 | #ifdef PS_USE_PRCTL 452 | prctl(PR_SET_NAME, title); 453 | #endif 454 | } 455 | 456 | 457 | void 458 | get_thread_title(char *title) 459 | { 460 | #ifdef PS_USE_PRCTL 461 | prctl(PR_GET_NAME, title); 462 | #endif 463 | } 464 | -------------------------------------------------------------------------------- /src/spt_status.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * spt_status.h 4 | * 5 | * Declarations for spt_status.c 6 | * 7 | *------------------------------------------------------------------------- 8 | */ 9 | 10 | #ifndef SPT_STATUS_H 11 | #define SPT_STATUS_H 12 | 13 | #include "c.h" 14 | 15 | HIDDEN extern bool update_process_title; 16 | 17 | HIDDEN extern char **save_ps_display_args(int argc, char **argv); 18 | 19 | HIDDEN extern void init_ps_display(const char *initial_str); 20 | 21 | HIDDEN extern void set_ps_display(const char *activity, bool force); 22 | 23 | HIDDEN extern const char *get_ps_display(size_t *displen); 24 | 25 | HIDDEN extern void set_thread_title(const char *title); 26 | 27 | HIDDEN extern void get_thread_title(char *title); 28 | 29 | #endif /* SPT_STATUS_H */ 30 | 31 | -------------------------------------------------------------------------------- /src/spt_strlcpy.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * strlcpy.c 4 | * strncpy done right 5 | * 6 | * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group 7 | * 8 | * 9 | * IDENTIFICATION 10 | * $PostgreSQL: pgsql/src/port/strlcpy.c,v 1.4 2007/01/05 22:20:03 momjian Exp $ 11 | * 12 | * This file was taken from OpenBSD and is used on platforms that don't 13 | * provide strlcpy(). The OpenBSD copyright terms follow. 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | /* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ 18 | 19 | /* 20 | * Copyright (c) 1998 Todd C. Miller 21 | * 22 | * Permission to use, copy, modify, and distribute this software for any 23 | * purpose with or without fee is hereby granted, provided that the above 24 | * copyright notice and this permission notice appear in all copies. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 27 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 28 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 29 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 30 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 31 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 32 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 33 | */ 34 | 35 | #include "c.h" 36 | 37 | 38 | /* 39 | * Copy src to string dst of size siz. At most siz-1 characters 40 | * will be copied. Always NUL terminates (unless siz == 0). 41 | * Returns strlen(src); if retval >= siz, truncation occurred. 42 | * Function creation history: http://www.gratisoft.us/todd/papers/strlcpy.html 43 | */ 44 | size_t 45 | spt_strlcpy(char *dst, const char *src, size_t siz) 46 | { 47 | char *d = dst; 48 | const char *s = src; 49 | size_t n = siz; 50 | 51 | /* Copy as many bytes as will fit */ 52 | if (n != 0) 53 | { 54 | while (--n != 0) 55 | { 56 | if ((*d++ = *s++) == '\0') 57 | break; 58 | } 59 | } 60 | 61 | /* Not enough room in dst, add NUL and traverse rest of src */ 62 | if (n == 0) 63 | { 64 | if (siz != 0) 65 | *d = '\0'; /* NUL-terminate dst */ 66 | while (*s++) 67 | ; 68 | } 69 | 70 | return (s - src - 1); /* count does not include NUL */ 71 | } 72 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrazzo/py-setproctitle/11d5ba71f0dc2bee2deb32ad17ac577101d404cd/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess as sp 4 | 5 | import pytest 6 | 7 | 8 | def pytest_configure(config): 9 | config.addinivalue_line( 10 | "markers", "embedded: the test create an embedded executable" 11 | ) 12 | 13 | 14 | skip_if_win32 = pytest.mark.skipif( 15 | "sys.platform == 'win32'", reason="skipping Posix tests on Windows" 16 | ) 17 | 18 | skip_if_macos = pytest.mark.skipif( 19 | "sys.platform == 'darwin'", reason="skipping test on macOS" 20 | ) 21 | 22 | skip_if_pypy = pytest.mark.skipif( 23 | "'__pypy__' in sys.builtin_module_names", reason="skipping test on pypy" 24 | ) 25 | 26 | skip_if_no_proc_env = pytest.mark.skipif( 27 | "not os.path.exists('/proc/self/environ')", 28 | reason="'/proc/self/environ' not available", 29 | ) 30 | 31 | skip_if_no_proc_cmdline = pytest.mark.skipif( 32 | "not os.path.exists('/proc/%s/cmdline' % os.getpid())", 33 | reason="'/proc/PID/cmdline' not available", 34 | ) 35 | 36 | skip_if_no_proc_tasks = pytest.mark.skipif( 37 | "not os.path.exists('/proc/self/task')", 38 | reason="'/proc/self/task' not available", 39 | ) 40 | 41 | 42 | @pytest.fixture(scope="session") 43 | def pyrun(pyconfig): 44 | """ 45 | Build the pyrun executable and return its path 46 | """ 47 | # poor man's make 48 | here = os.path.abspath(os.path.dirname(__file__)) 49 | ver2 = "%s.%s" % sys.version_info[:2] 50 | source = os.path.join(here, "pyrun.c") 51 | target = os.path.join(here, f"pyrun{ver2}") 52 | if ( 53 | os.path.exists(target) 54 | and os.stat(target).st_mtime > os.stat(source).st_mtime 55 | ): 56 | return target 57 | 58 | cmdline = ["cc"] # big punt 59 | cmdline.extend(pyconfig("includes")) 60 | cmdline.extend(["-o", target, source]) 61 | cmdline.extend(pyconfig("ldflags")) 62 | cmdline.append(f"-L{pyconfig('prefix')[0]}/lib") 63 | sp.check_call(cmdline) 64 | return target 65 | 66 | 67 | @pytest.fixture(scope="session") 68 | def pyconfig(): 69 | """Return the result of 'python-config --opt' as a list of strings""" 70 | pyexe = os.path.realpath(sys.executable) 71 | ver2 = "%s.%s" % sys.version_info[:2] 72 | for name in (f"python{ver2}-config", "python3-config", "python-config"): 73 | pyconfexe = os.path.join(os.path.dirname(pyexe), name) 74 | if os.path.exists(pyconfexe): 75 | break 76 | else: 77 | pytest.fail( 78 | f"can't find python-config from executable {sys.executable}" 79 | ) 80 | 81 | # Travis' Python 3.8 is not built with --embed 82 | help = sp.check_output([pyconfexe, "--help"]) 83 | has_embed = b"--embed" in help 84 | 85 | def pyconfig_func(opt): 86 | cmdline = [pyconfexe, f"--{opt}"] 87 | if has_embed: 88 | cmdline.append("--embed") 89 | bout = sp.check_output(cmdline) 90 | out = bout.decode( 91 | sys.getfilesystemencoding() # sounds like a good bet 92 | ) 93 | return out.split() 94 | 95 | return pyconfig_func 96 | 97 | 98 | @pytest.fixture(scope="session") 99 | def spt_directory(): 100 | """ 101 | Where is the setproctitle module installed? 102 | """ 103 | rv = run_script( 104 | """ 105 | import os 106 | import setproctitle 107 | print(os.path.dirname(os.path.dirname(setproctitle.__file__))) 108 | """ 109 | ) 110 | return rv.rstrip() 111 | 112 | 113 | @pytest.fixture(scope="function") 114 | def tmp_pypath(monkeypatch, tmp_path): 115 | """ 116 | return a tmp directory which has been added to the python path 117 | """ 118 | monkeypatch.setenv( 119 | "PYTHONPATH", 120 | str(tmp_path) + os.pathsep + os.environ.get("PYTHONPATH", ""), 121 | ) 122 | return tmp_path 123 | 124 | 125 | def run_script(script=None, args=None, executable=None, env=None): 126 | """run a script in a separate process. 127 | 128 | if the script completes successfully, return its ``stdout``, 129 | else fail the test. 130 | """ 131 | if executable is None: 132 | executable = sys.executable 133 | 134 | cmdline = str(executable) 135 | if args: 136 | cmdline = cmdline + " " + args 137 | 138 | proc = sp.Popen( 139 | cmdline, 140 | stdin=sp.PIPE, 141 | stdout=sp.PIPE, 142 | stderr=sp.PIPE, 143 | env=env, 144 | shell=True, 145 | close_fds=True, 146 | ) 147 | 148 | out, err = proc.communicate(script and script.encode()) 149 | if 0 != proc.returncode: 150 | if out: 151 | print(out.decode("utf8", "replace"), file=sys.stdout) 152 | if err: 153 | print(err.decode("utf8", "replace"), file=sys.stderr) 154 | pytest.fail("test script failed") 155 | 156 | # Py3 subprocess generates bytes strings. 157 | out = out.decode() 158 | 159 | return out 160 | -------------------------------------------------------------------------------- /tests/module_test.py: -------------------------------------------------------------------------------- 1 | from .conftest import run_script, skip_if_win32 2 | 3 | 4 | @skip_if_win32 5 | def test_no_import_side_effect(): 6 | """ 7 | Check that importing the module doesn't cause side effects. 8 | """ 9 | rv = run_script( 10 | """ 11 | import os 12 | 13 | def print_stuff(): 14 | for fn in "cmdline status comm".split(): 15 | if os.path.exists(f"/proc/self/{fn}"): 16 | with open(f"/proc/self/{fn}") as f: 17 | print(f.readline().rstrip()) 18 | 19 | print_stuff() 20 | print("---") 21 | import setproctitle 22 | print_stuff() 23 | """ 24 | ) 25 | before, after = rv.split("---\n") 26 | assert before == after 27 | 28 | 29 | def test_version(): 30 | """Test that the module has a version""" 31 | rv = run_script( 32 | """ 33 | import setproctitle 34 | print(setproctitle.__version__) 35 | """ 36 | ) 37 | assert rv 38 | 39 | 40 | def test_c_extension_built(): 41 | """Test that the C extension was built""" 42 | rv = run_script( 43 | """ 44 | from setproctitle import _setproctitle 45 | """ 46 | ) 47 | assert rv == "" 48 | -------------------------------------------------------------------------------- /tests/pyrun.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * pyrun.c 4 | * Stand-alone program to test with embedded Python. 5 | * 6 | * Run a Python program read from stdin. In case of error return 1. 7 | * 8 | * Copyright (c) 2011 Daniele Varrazzo 9 | * 10 | *------------------------------------------------------------------------- 11 | */ 12 | 13 | #include 14 | 15 | int 16 | main(int argc, char *argv[]) 17 | { 18 | int rv = 0; 19 | 20 | Py_Initialize(); 21 | 22 | if (0 != PyRun_SimpleFile(stdin, "stdin")) { 23 | rv = 1; 24 | } 25 | 26 | Py_Finalize(); 27 | 28 | return rv; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /tests/setproctitle_test.py: -------------------------------------------------------------------------------- 1 | """setproctitle module unit test. 2 | 3 | Use pytest to run this test suite. 4 | 5 | The tests are executed in external processes: setproctitle should 6 | never be imported directly from here. 7 | 8 | Copyright (c) 2009 Daniele Varrazzo 9 | """ 10 | 11 | import os 12 | import re 13 | import sys 14 | import string 15 | import subprocess as sp 16 | 17 | import pytest 18 | 19 | from .conftest import run_script, skip_if_no_proc_cmdline, skip_if_no_proc_env 20 | from .conftest import skip_if_macos, skip_if_pypy, skip_if_win32 21 | 22 | pytestmark = [skip_if_win32] 23 | 24 | 25 | def test_runner(): 26 | """Test the script execution method.""" 27 | rv = run_script( 28 | """ 29 | print(10 + 20) 30 | """ 31 | ) 32 | assert rv == "30\n" 33 | 34 | 35 | @pytest.mark.skipif( 36 | 'sys.platform == "darwin" and (os.environ.get("CIBW_TEST_COMMAND") or sys.version_info >= (3, 11))', 37 | reason="f*cked up binary name", 38 | ) 39 | def test_init_getproctitle(): 40 | """getproctitle() returns a sensible value at initial call.""" 41 | rv = run_script( 42 | """ 43 | import setproctitle 44 | print(setproctitle.getproctitle()) 45 | """, 46 | args="-u", 47 | ) 48 | assert rv == sys.executable + " -u\n" 49 | 50 | 51 | def test_setproctitle(): 52 | """setproctitle() can set the process title, duh.""" 53 | rv = run_script( 54 | r""" 55 | import setproctitle 56 | setproctitle.setproctitle('Hello, world!') 57 | 58 | import os 59 | print(os.getpid()) 60 | # ps can fail on kfreebsd arch 61 | # (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=460331) 62 | print(os.popen("ps -x -o pid,command 2> /dev/null").read()) 63 | """ 64 | ) 65 | lines = [line for line in rv.splitlines() if line] 66 | pid = lines.pop(0) 67 | pids = dict([r.strip().split(None, 1) for r in lines]) 68 | title = _clean_up_title(pids[pid]) 69 | assert title == "Hello, world!" 70 | 71 | 72 | @pytest.mark.skipif('sys.platform != "darwin"', reason="Mac only test") 73 | def test_setproctitle_darwin(): 74 | """Mac Activity monitor shows correct info""" 75 | rv = run_script( 76 | r""" 77 | import setproctitle 78 | setproctitle.setproctitle('QwErTyZxCvB') 79 | 80 | import os 81 | print(os.popen("lsappinfo find name=QwErTyZxCvB 2> /dev/null").read()) 82 | """ 83 | ) 84 | m = re.search(r'''ASN:.*"QwErTyZxCvB"''', rv) 85 | assert m 86 | 87 | 88 | def test_prctl(): 89 | """Check that prctl is called on supported platforms.""" 90 | linux_version = [] 91 | if sys.platform in ("linux", "linux2"): 92 | try: 93 | f = os.popen("uname -r") 94 | name = f.read() 95 | f.close() 96 | except Exception: 97 | pass 98 | else: 99 | linux_version = list( 100 | map(int, re.search("[.0-9]+", name).group().split(".")[:3]) 101 | ) 102 | 103 | if linux_version < [2, 6, 9]: 104 | pytest.skip("syscall not supported") 105 | 106 | rv = run_script( 107 | r""" 108 | import setproctitle 109 | setproctitle.setproctitle('Hello, prctl!') 110 | print(open('/proc/self/status').read()) 111 | """ 112 | ) 113 | status = dict([r.split(":", 1) for r in rv.splitlines() if ":" in r]) 114 | assert status["Name"].strip() == "Hello, prctl!" 115 | 116 | 117 | def test_getproctitle(): 118 | """getproctitle() can read the process title back.""" 119 | rv = run_script( 120 | r""" 121 | import setproctitle 122 | setproctitle.setproctitle('Hello, world!') 123 | print(setproctitle.getproctitle()) 124 | """ 125 | ) 126 | assert rv == "Hello, world!\n" 127 | 128 | 129 | def test_kwarg(): 130 | """setproctitle() supports keyword args.""" 131 | rv = run_script( 132 | r""" 133 | import setproctitle 134 | setproctitle.setproctitle(title='Hello, world!') 135 | print(setproctitle.getproctitle()) 136 | """ 137 | ) 138 | assert rv == "Hello, world!\n" 139 | 140 | 141 | def test_environ(): 142 | """Check that clobbering environ didn't break env.""" 143 | rv = run_script( 144 | r""" 145 | import setproctitle 146 | setproctitle.setproctitle('Hello, world! ' + 'X' * 1024) 147 | 148 | # set a new env variable, update another one 149 | import os 150 | os.environ['TEST_SETENV'] = "setenv-value" 151 | os.environ['PATH'] = os.environ.get('PATH', '') \ 152 | + os.pathsep + "fakepath" 153 | 154 | # read the environment from a spawned process inheriting the 155 | # updated env 156 | newenv = dict([r.split("=",1) 157 | for r in os.popen("env").read().splitlines() 158 | if '=' in r]) 159 | 160 | print(setproctitle.getproctitle()) 161 | print(newenv['TEST_SETENV']) 162 | print(newenv['PATH']) 163 | """ 164 | ) 165 | 166 | title, test, path = rv.splitlines() 167 | assert title.startswith("Hello, world! XXXXX"), title 168 | assert test == "setenv-value" 169 | assert path.endswith("fakepath"), path 170 | 171 | 172 | def test_issue_8(tmp_pypath): 173 | """Test that the module works with 'python -m'.""" 174 | module = "spt_issue_8" 175 | with open(tmp_pypath / f"{module}.py", "w") as f: 176 | f.write( 177 | r""" 178 | import setproctitle 179 | setproctitle.setproctitle("Hello, module!") 180 | 181 | import os 182 | print(os.getpid()) 183 | print(os.popen("ps -x -o pid,command 2> /dev/null").read()) 184 | """ 185 | ) 186 | 187 | rv = run_script(args="-m " + module) 188 | lines = [line for line in rv.splitlines() if line] 189 | pid = lines.pop(0) 190 | pids = dict([r.strip().split(None, 1) for r in lines]) 191 | 192 | title = _clean_up_title(pids[pid]) 193 | assert title == "Hello, module!" 194 | 195 | 196 | def test_large_cmdline(tmp_pypath): 197 | """Test with a 64KB command line.""" 198 | module = "longargs" 199 | with open(tmp_pypath / f"{module}.py", "w") as f: 200 | f.write( 201 | r""" 202 | import setproctitle 203 | setproctitle.setproctitle("Hello, long!") 204 | 205 | import os 206 | print(os.getpid()) 207 | print(os.popen("ps -x -o pid,command 2> /dev/null").read()) 208 | """ 209 | ) 210 | 211 | rv = run_script(args=f"-m {module} {' '.join(['X' * 1024] * 64)}") 212 | lines = [line for line in rv.splitlines() if line] 213 | pid = lines.pop(0) 214 | pids = dict([r.strip().split(None, 1) for r in lines]) 215 | 216 | title = _clean_up_title(pids[pid]) 217 | assert title == "Hello, long!" 218 | 219 | 220 | def test_unicode(): 221 | """Title can contain unicode characters.""" 222 | snowman = "\u2603" 223 | try: 224 | snowman.encode(sys.getdefaultencoding()) 225 | except UnicodeEncodeError: 226 | pytest.skip( 227 | "default encoding '%s' can't deal with snowmen" 228 | % sys.getdefaultencoding() 229 | ) 230 | 231 | try: 232 | snowman.encode(sys.getfilesystemencoding()) 233 | except UnicodeEncodeError: 234 | pytest.skip( 235 | "file system encoding '%s' can't deal with snowmen" 236 | % sys.getfilesystemencoding() 237 | ) 238 | 239 | rv = run_script( 240 | r""" 241 | snowman = u'\u2603' 242 | 243 | import setproctitle 244 | setproctitle.setproctitle("Hello, " + snowman + "!") 245 | 246 | import os 247 | import locale 248 | from subprocess import Popen, PIPE 249 | print(os.getpid()) 250 | proc = Popen("ps -x -o pid,command 2> /dev/null", shell=True, 251 | close_fds=True, stdout=PIPE, stderr=PIPE) 252 | buf = proc.stdout.read() 253 | print(buf.decode(locale.getpreferredencoding(), 'replace')) 254 | """ 255 | ) 256 | lines = [line for line in rv.splitlines() if line] 257 | pid = lines.pop(0) 258 | pids = dict([r.strip().split(None, 1) for r in lines]) 259 | 260 | snowmen = [ 261 | "\u2603", # ps supports unicode 262 | r"\M-b\M^X\M^C", # ps output on BSD 263 | r"M-bM^XM^C", # ps output on some Darwin < 11.2 264 | "\ufffdM^XM^C", # ps output on Darwin 11.2 265 | ] 266 | title = _clean_up_title(pids[pid]) 267 | for snowman in snowmen: 268 | if title == "Hello, " + snowman + "!": 269 | break 270 | else: 271 | pytest.fail("unexpected ps output: %r" % title) 272 | 273 | 274 | def test_weird_args(): 275 | """No problem with encoded arguments.""" 276 | euro = "\u20ac" 277 | snowman = "\u2603" 278 | try: 279 | rv = run_script( 280 | r""" 281 | import setproctitle 282 | setproctitle.setproctitle("Hello, weird args!") 283 | 284 | import os 285 | print(os.getpid()) 286 | print(os.popen("ps -x -o pid,command 2> /dev/null").read()) 287 | """, 288 | args=" ".join(["-", "hello", euro, snowman]), 289 | ) 290 | except TypeError: 291 | pytest.skip("apparently we can't pass unicode args to a program") 292 | 293 | lines = [line for line in rv.splitlines() if line] 294 | pid = lines.pop(0) 295 | pids = dict([r.strip().split(None, 1) for r in lines]) 296 | 297 | title = _clean_up_title(pids[pid]) 298 | assert title == "Hello, weird args!" 299 | 300 | 301 | def test_weird_path(tmp_path, spt_directory): 302 | """No problem with encoded argv[0] path.""" 303 | _check_4388() 304 | euro = "\u20ac" 305 | snowman = "\u2603" 306 | dir = tmp_path / euro / snowman 307 | try: 308 | os.makedirs(dir) 309 | except UnicodeEncodeError: 310 | pytest.skip("file system doesn't support unicode") 311 | 312 | exc = dir / os.path.basename(sys.executable) 313 | os.symlink(sys.executable, exc) 314 | 315 | rv = run_script( 316 | f""" 317 | import sys 318 | sys.path.insert(0, {repr(spt_directory)}) 319 | 320 | import setproctitle 321 | setproctitle.setproctitle("Hello, weird path!") 322 | 323 | import os 324 | print(os.getpid()) 325 | print(os.popen("ps -x -o pid,command 2> /dev/null").read()) 326 | """, 327 | args=" ".join(["-", "foo", "bar", "baz"]), 328 | executable=exc, 329 | ) 330 | lines = [line for line in rv.splitlines() if line] 331 | pid = lines.pop(0) 332 | pids = dict([r.strip().split(None, 1) for r in lines]) 333 | 334 | title = _clean_up_title(pids[pid]) 335 | assert title == "Hello, weird path!" 336 | 337 | 338 | @pytest.mark.embedded 339 | @skip_if_pypy 340 | @skip_if_macos 341 | @skip_if_no_proc_cmdline 342 | def test_embedded(pyrun, spt_directory): 343 | """Check the module works with embedded Python.""" 344 | rv = run_script( 345 | f""" 346 | import sys 347 | sys.path.insert(0, {spt_directory!r}) 348 | import setproctitle 349 | setproctitle.setproctitle("Hello, embedded!") 350 | 351 | import os 352 | print(os.getpid()) 353 | print(os.popen("ps -x -o pid,command 2> /dev/null").read()) 354 | """, 355 | executable=pyrun, 356 | ) 357 | lines = [line for line in rv.splitlines() if line] 358 | pid = lines.pop(0) 359 | pids = dict([r.strip().split(None, 1) for r in lines]) 360 | 361 | title = _clean_up_title(pids[pid]) 362 | assert title == "Hello, embedded!" 363 | 364 | 365 | @pytest.mark.embedded 366 | @skip_if_pypy 367 | @skip_if_macos 368 | @skip_if_no_proc_cmdline 369 | def test_embedded_many_args(pyrun, spt_directory): 370 | """Check more complex cmdlines are handled in embedded env too.""" 371 | rv = run_script( 372 | f""" 373 | import sys 374 | sys.path.insert(0, {spt_directory!r}) 375 | import setproctitle 376 | setproctitle.setproctitle("Hello, embedded!") 377 | 378 | import os 379 | print(os.getpid()) 380 | print(os.popen("ps -x -o pid,command 2> /dev/null").read()) 381 | """, 382 | executable=pyrun, 383 | args=" ".join(["foo", "bar", "baz"]), 384 | ) 385 | lines = [line for line in rv.splitlines() if line] 386 | pid = lines.pop(0) 387 | pids = dict([r.strip().split(None, 1) for r in lines]) 388 | 389 | title = _clean_up_title(pids[pid]) 390 | assert title == "Hello, embedded!" 391 | 392 | 393 | @skip_if_no_proc_env 394 | def test_noenv(): 395 | """Check that SPT_NOENV avoids clobbering environ.""" 396 | env = os.environ.copy() 397 | env["SPT_TESTENV"] = "testenv" 398 | rv = run_script( 399 | """ 400 | import os 401 | os.environ['SPT_NOENV'] = "1" 402 | 403 | cmdline_len = len(open('/proc/self/cmdline').read()) 404 | print(cmdline_len) 405 | print('SPT_TESTENV=testenv' in open('/proc/self/environ').read()) 406 | 407 | import setproctitle 408 | setproctitle.setproctitle('X' * cmdline_len * 10) 409 | 410 | title = open('/proc/self/cmdline').read().rstrip() 411 | print(title) 412 | print(len(title)) 413 | 414 | print('SPT_TESTENV=testenv' in open('/proc/self/environ').read()) 415 | """, 416 | env=env, 417 | ) 418 | lines = rv.splitlines() 419 | cmdline_len = int(lines[0]) 420 | assert lines[1] == "True", "can't verify testenv" 421 | title = lines[2] 422 | assert "XXX" in _clean_up_title(title), "title not set as expected" 423 | title_len = int(lines[3]) 424 | assert lines[4] == "True", "env has been clobbered" 425 | assert ( 426 | title_len <= cmdline_len 427 | ), "title (len {title_len}) not limited to argv (len {cmdline_len})" 428 | 429 | 430 | @skip_if_no_proc_env 431 | def test_large_env(monkeypatch): 432 | """Check that large environment doesn't get clobbered.""" 433 | monkeypatch.setenv("SPT_NOENV", "1") 434 | for c in string.ascii_uppercase: 435 | monkeypatch.setenv( 436 | f"{c}_TEST_ENV", "X" * (ord(c) - ord("A") + 1) * 1024 437 | ) 438 | 439 | rv = run_script( 440 | r"""\ 441 | import sys 442 | with open("/proc/self/environ", "rb") as f: 443 | env1 = f.read() 444 | sys.stdout.buffer.write(env1) 445 | 446 | sys.stdout.buffer.write(b"\n-----8<-----\n") 447 | 448 | import setproctitle 449 | setproctitle.setproctitle("hello") 450 | 451 | with open("/proc/self/environ", "rb") as f: 452 | env2 = f.read() 453 | sys.stdout.buffer.write(env2) 454 | """ 455 | ) 456 | parts = rv.split("\n-----8<-----\n") 457 | for i, part in enumerate(parts): 458 | parts[i] = dict( 459 | var.split("=", 1) for var in part.split("\0") if "=" in var 460 | ) 461 | 462 | assert parts[0] == parts[1] 463 | 464 | 465 | def test_clear_segfault(): 466 | run_script( 467 | r"""\ 468 | import os 469 | from setproctitle import setproctitle 470 | os.environ.clear() 471 | setproctitle("Test") 472 | """ 473 | ) 474 | 475 | 476 | def test_fork_segfault(): 477 | run_script( 478 | """\ 479 | import multiprocessing as mp 480 | from setproctitle import setproctitle 481 | 482 | def foo(): 483 | setproctitle('title in child') 484 | 485 | setproctitle('title in parent') 486 | mp.set_start_method("fork") 487 | p = mp.Process(target=foo) 488 | p.start() 489 | p.join() 490 | assert p.exitcode == 0, f"p.exitcode is {p.exitcode}" 491 | """ 492 | ) 493 | 494 | 495 | def test_thread_fork_segfault(): 496 | run_script( 497 | """\ 498 | import multiprocessing as mp 499 | from threading import Thread 500 | from setproctitle import setproctitle 501 | 502 | def foo(): 503 | setproctitle("title in child") 504 | 505 | def thread(): 506 | global p 507 | p = mp.Process(target=foo) 508 | p.start() 509 | p.join() 510 | 511 | p = None 512 | mp.set_start_method("fork") 513 | t = Thread(target=thread) 514 | t.start() 515 | t.join() 516 | assert p.exitcode == 0, f"p.exitcode is {p.exitcode}" 517 | """ 518 | ) 519 | 520 | 521 | # Support functions 522 | 523 | 524 | def _clean_up_title(title): 525 | """Clean up a string from the prefix added by the platform.""" 526 | # BSD's setproctitle decorates the title with the process name. 527 | if "bsd" in sys.platform: 528 | procname = os.path.basename(sys.executable) 529 | title = " ".join([t for t in title.split(" ") if procname not in t]) 530 | 531 | return title 532 | 533 | 534 | def _check_4388(): 535 | """Check if the system is affected by bug #4388. 536 | 537 | If positive, unicode chars in the cmdline are not reliable, 538 | so bail out. 539 | 540 | see: http://bugs.python.org/issue4388 541 | """ 542 | if sys.getfilesystemencoding() == "ascii": 543 | # in this case the char below would get translated in some 544 | # inconsistent way. 545 | # I'm not getting why the FS encoding is involved in process 546 | # spawning, the whole story just seems a gigantic can of worms. 547 | return 548 | 549 | p = sp.Popen([sys.executable, "-c", "ord('\xe9')"], stderr=sp.PIPE) 550 | p.communicate() 551 | if p.returncode: 552 | pytest.skip("bug #4388 detected") 553 | -------------------------------------------------------------------------------- /tests/setthreadtitle_test.py: -------------------------------------------------------------------------------- 1 | import os # noqa 2 | import sys # noqa 3 | 4 | from .conftest import run_script, skip_if_win32, skip_if_no_proc_tasks 5 | 6 | pytestmark = [skip_if_win32, skip_if_no_proc_tasks] 7 | 8 | 9 | def test_thread_title_unchanged(): 10 | rv = run_script( 11 | """ 12 | from glob import glob 13 | 14 | def print_stuff(): 15 | for fn in sorted(glob("/proc/self/task/*/comm")): 16 | with open(fn) as f: 17 | print(f.readline().rstrip()) 18 | 19 | print_stuff() 20 | print("---") 21 | import setproctitle 22 | print_stuff() 23 | print("---") 24 | print(setproctitle.getthreadtitle()) 25 | """ 26 | ) 27 | before, after, gtt = rv.split("---\n") 28 | assert before == after 29 | assert before == gtt 30 | 31 | 32 | def test_set_thread_title(): 33 | run_script( 34 | """ 35 | from glob import glob 36 | import setproctitle 37 | setproctitle.setthreadtitle("hello" * 10) 38 | 39 | (fn,) = glob("/proc/self/task/*/comm") 40 | with open(fn) as f: 41 | assert f.read().rstrip() == "hello" * 3 42 | """ 43 | ) 44 | 45 | 46 | def test_set_threads_title(): 47 | run_script( 48 | """ 49 | import time 50 | import threading 51 | from glob import glob 52 | 53 | (fn,) = glob("/proc/self/task/*/comm") 54 | with open(fn) as f: 55 | orig = f.read().rstrip() 56 | 57 | import setproctitle 58 | 59 | def worker(title): 60 | setproctitle.setthreadtitle(title) 61 | while 1: 62 | time.sleep(1) 63 | 64 | t1 = threading.Thread(target=worker, args=('reader',), daemon=True) 65 | t2 = threading.Thread(target=worker, args=('writer',), daemon=True) 66 | t1.start() 67 | t2.start() 68 | 69 | comms = [] 70 | for fn in glob("/proc/self/task/*/comm"): 71 | with open(fn) as f: 72 | comms.append(f.read().rstrip()) 73 | 74 | comms.sort() 75 | assert comms == sorted([orig, "reader", "writer"]) 76 | """ 77 | ) 78 | -------------------------------------------------------------------------------- /tests/test_win32.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import setproctitle 3 | import sys # noqa 4 | 5 | skip_if_not_win32 = pytest.mark.skipif( 6 | "sys.platform != 'win32'", reason="Windows only test" 7 | ) 8 | 9 | pytestmark = [skip_if_not_win32] 10 | 11 | 12 | def test_setproctitle(): 13 | title = "setproctitle_test" 14 | setproctitle.setproctitle(title) 15 | assert title == setproctitle.getproctitle() 16 | 17 | 18 | def test_setthreadtitle(): 19 | title = "setproctitle_test" 20 | # This is currently a no-op on Windows. Let's make sure 21 | # that at least it doesn't error out. 22 | setproctitle.setthreadtitle(title) 23 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | PTTEST_OBJS = pttest.o 2 | SPTDEMO_OBJS = spt_demo.o ../src/spt_status.o ../src/strlcpy.o 3 | 4 | PS_STATUS_C_URL = "http://git.postgresql.org/gitweb?p=postgresql.git;a=blob_plain;f=src/backend/utils/misc/ps_status.c;hb=HEAD" 5 | PS_STATUS_H_URL = "http://git.postgresql.org/gitweb?p=postgresql.git;a=blob_plain;f=src/include/utils/ps_status.h;hb=HEAD" 6 | 7 | CFLAGS = -g -Wall 8 | 9 | CURL = wget -O - 10 | 11 | 12 | all: pttest sptdemo prctl_demo 13 | 14 | pttest: $(PTTEST_OBJS) 15 | 16 | sptdemo: $(SPTDEMO_OBJS) 17 | $(CC) $(LDFLAGS) $^ -o $@ 18 | 19 | pgsources: ps_status.c ps_status.h 20 | 21 | ps_status.c : 22 | $(CURL) $(PS_STATUS_C_URL) > $@ 23 | 24 | ps_status.h : 25 | $(CURL) $(PS_STATUS_H_URL) > $@ 26 | 27 | -------------------------------------------------------------------------------- /tools/prctl_demo.c: -------------------------------------------------------------------------------- 1 | /* A test to check what happens using ``prctl()``. 2 | * 3 | * The ``prctl()`` call is available in Linux from 2.6.9. 4 | * 5 | * See http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html 6 | */ 7 | 8 | #include /* for prctl() */ 9 | #include /* for PR_SET_NAME */ 10 | 11 | #include 12 | #include 13 | 14 | int 15 | main(int argc, char **argv) 16 | { 17 | printf("Process PID: %i\n", getpid()); 18 | 19 | prctl(PR_SET_NAME, "Hello world"); 20 | printf("Title changed, press enter\n"); 21 | getchar(); 22 | 23 | /* The string set by prctl can be read in ``/proc/PID/stat`` 24 | * and ``/proc/PID/status``. It is displayed by ``ps`` but not by ``ps a`` 25 | * (which instead displays the content of ``/proc/PID/cmdline``). ``top`` 26 | * toggles between both visualizations pressing ``c``. 27 | */ 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /tools/pttest.c: -------------------------------------------------------------------------------- 1 | /* trying to understand why in order to clobber argv 2 | * postgres moves around environ too. 3 | */ 4 | #include 5 | 6 | extern char **environ; 7 | 8 | int 9 | main(int argc, char **argv) 10 | { 11 | char **p; 12 | printf("argv: %p\n", argv); 13 | printf("environ: %p\n", environ); 14 | for (p = argv; *p; ++p) { 15 | printf("argv[%i]: %p (%s)\n", p - argv, *p, *p); 16 | } 17 | for (p = environ; *p; ++p) { 18 | printf("environ[%i]: %p (%s)\n", p - environ, *p, *p); 19 | } 20 | 21 | /* My conclusion is that environ is contiguous to argv */ 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /tools/spt_demo.c: -------------------------------------------------------------------------------- 1 | /* A small demo to show how to use the display change */ 2 | 3 | #include "../src/spt_status.h" 4 | 5 | #include 6 | #include 7 | 8 | int 9 | main(int argc, char **argv) 10 | { 11 | printf("Process PID: %i\n", getpid()); 12 | 13 | argv = save_ps_display_args(argc, argv); 14 | init_ps_display("hello, world"); 15 | printf("Title changed, press enter\n"); 16 | getchar(); 17 | 18 | set_ps_display("new title!", true); 19 | printf("Title changed again, press enter to exit\n"); 20 | getchar(); 21 | 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t, pypy-3.8, pypy-3.9, pypy-3.10 3 | 4 | [testenv] 5 | commands = 6 | pytest {posargs} 7 | extras = 8 | test 9 | 10 | [testenv:3.8] 11 | basepython = python3.8 12 | 13 | [testenv:3.9] 14 | basepython = python3.9 15 | 16 | [testenv:3.10] 17 | basepython = python3.10 18 | 19 | [testenv:3.11] 20 | basepython = python3.11 21 | 22 | [testenv:3.12] 23 | basepython = python3.12 24 | 25 | [testenv:3.13] 26 | basepython = python3.13 27 | 28 | [testenv:3.13t] 29 | basepython = python3.13t 30 | 31 | [testenv:pypy-3.8] 32 | basepython = pypy3.8 33 | 34 | [testenv:pypy-3.9] 35 | basepython = pypy3.9 36 | 37 | [testenv:pypy-3.10] 38 | basepython = pypy3.10 39 | 40 | [testenv:xcode] 41 | basepython = {env:XCODE_PYTHON} 42 | --------------------------------------------------------------------------------