• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bleachbit / bleachbit / 29651293207

18 Jul 2026 04:06PM UTC coverage: 76.417% (+0.1%) from 76.305%
29651293207

push

github

web-flow
Merge pull request #2215 from XhmikosR/xmr/fixes-misc

Misc fixes

- The **F11** key to toggle full screen in the GTK GUI recorded the old
  value, so when restarting the application, the wrong preference was restored.

- pacman_cache always reported 0 bytes.

- Several crashes are prevented (though not known to be triggered):
  - notify()
  - /proc enumeration,
  - uris_to_paths on short file URIs
  - Wine .desktop parsing
  - Windows startup from foldername containing "lib" or "bin"

- A single unknown option in Winapp2.ini aborted the whole section. There are
  no such options in the file Winapp2.ini distributed now, but it could affect
  custom files or future files.

42 of 74 new or added lines in 13 files covered. (56.76%)

1 existing line in 1 file now uncovered.

8198 of 10728 relevant lines covered (76.42%)

1.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.77
/bleachbit/Process.py
1
# SPDX-License-Identifier: GPL-3.0-or-later
2
# Copyright (c) 2008-2026 Andrew Ziem.
3
#
4
# This work is licensed under the terms of the GNU GPL, version 3 or
5
# later.  See the COPYING file in the top-level directory.
6

7
"""
8
Enumerate and terminate processes
9
"""
10

11
import signal
2✔
12
import glob
2✔
13
import subprocess
2✔
14
from collections import namedtuple
2✔
15
import os
2✔
16

17
from bleachbit import IS_LINUX, IS_POSIX, IS_WINDOWS
2✔
18

19
ProcessInfo = namedtuple('ProcessInfo', ['pid', 'name', 'same_user'])
2✔
20

21
try:
2✔
22
    import psutil
2✔
23
    _has_psutil = True
2✔
24
except ImportError:
2✔
25
    _has_psutil = False
2✔
26

27

28
def enumerate_processes():
2✔
29
    """Yield ProcessInfo(pid, name, same_user) for all accessible processes.
30

31
    'same_user' is True if the process owner matches the current (real) user.
32
    On Unix with sudo, compares against the non-root user.
33
    """
34
    if _has_psutil and IS_POSIX:
2✔
35
        yield from _enumerate_psutil_posix()
1✔
36
        return
1✔
37
    # Windows should always have psutil.
38
    if _has_psutil and IS_WINDOWS:
1✔
39
        yield from _enumerate_psutil_windows()
1✔
40
        return
1✔
41
    if IS_LINUX:
1✔
42
        yield from _enumerate_proc_fs()
×
43
        return
×
44
    if IS_POSIX:
1✔
45
        yield from _enumerate_ps_aux()
×
46
        return
×
47
    raise RuntimeError('no method to enumerate processes on this system')
1✔
48

49

50
def _enumerate_psutil_posix():
2✔
51
    """Enumerate processes with psutils on POSIX"""
52
    from bleachbit.General import get_real_uid
1✔
53
    target_uid = get_real_uid()
1✔
54
    for proc in psutil.process_iter(['name', 'exe', 'uids']):
1✔
55
        try:
1✔
56
            name = proc.info['name']
1✔
57
            if not name:
1✔
58
                continue
×
59
            exe = proc.info.get('exe')
1✔
60
            uids = proc.info.get('uids')
1✔
61
            same_user = uids is not None and uids.real == target_uid
1✔
62
            yield ProcessInfo(proc.pid, name, same_user)
1✔
63
            # exe basename may differ from name (e.g. truncated comm)
64
            if exe and os.path.basename(exe) != name:
1✔
65
                yield ProcessInfo(proc.pid, os.path.basename(exe), same_user)
1✔
66
        except (psutil.NoSuchProcess, psutil.AccessDenied):
1✔
67
            continue
×
68

69

70
def _enumerate_psutil_windows():
2✔
71
    """Enumerate processes with psutils on Windows"""
72

73
    current_user = psutil.Process().username().lower()
1✔
74
    for proc in psutil.process_iter(['name', 'username']):
1✔
75
        try:
1✔
76
            name = proc.info['name']
1✔
77
            if not name:
1✔
78
                continue
1✔
79
            same_user = (proc.info['username'] or '').lower() == current_user
1✔
80
            yield ProcessInfo(proc.pid, name, same_user)
1✔
81
        except (psutil.NoSuchProcess, psutil.AccessDenied):
1✔
82
            continue
×
83

84

85
def _enumerate_proc_fs():
2✔
86
    """/proc filesystem strategy (Linux)"""
87
    from bleachbit.General import get_real_uid
1✔
88
    target_uid = get_real_uid()
1✔
89
    for filename in glob.iglob("/proc/*/exe"):
1✔
90
        pid_dir = os.path.dirname(filename)
1✔
91
        base = os.path.basename(pid_dir)
1✔
92
        if not base.isdigit():
1✔
93
            # skip /proc/self and /proc/thread-self
NEW
94
            continue
×
95
        pid = int(base)
1✔
96
        name = None
1✔
97
        try:
1✔
98
            target = os.path.realpath(filename)
1✔
99
            # Google Chrome 74 on Ubuntu 19.04 showed up as
100
            # /opt/google/chrome/chrome (deleted)
101
            name = os.path.basename(target).replace(' (deleted)', '')
×
102
        except OSError:
1✔
103
            # 13 = permission denied
104
            pass
1✔
105
        except TypeError:
×
106
            # TypeError happens, for example, when link points to
107
            # '/etc/password\x00 (deleted)'
108
            pass
×
109

110
        if not name:
1✔
111
            try:
1✔
112
                with open(os.path.join(pid_dir, 'stat'), 'r',
1✔
113
                          encoding='utf-8') as f:
114
                    stat_content = f.read()
1✔
115
                # comm is field 2, wrapped in parens, and may contain spaces
116
                name = stat_content[stat_content.index('(') + 1:
1✔
117
                                    stat_content.rindex(')')]
NEW
118
            except (OSError, ValueError):
×
119
                continue
×
120
        same_user = False
1✔
121
        try:
1✔
122
            same_user = os.stat(pid_dir).st_uid == target_uid
1✔
123
        except OSError:
×
124
            pass
×
125
        yield ProcessInfo(pid, name, same_user)
1✔
126

127

128
def _enumerate_ps_aux():
2✔
129
    """ps aux strategy (BSD/macOS)"""
130
    from bleachbit.General import get_real_username
1✔
131
    current_user = get_real_username()
1✔
132
    ps_out = subprocess.check_output(
1✔
133
        ["ps", "aux", "-c"], universal_newlines=True)
134
    first_line = ps_out.split('\n', maxsplit=1)[0].strip()
1✔
135
    if "USER" not in first_line or "COMMAND" not in first_line:
1✔
136
        raise RuntimeError("Unexpected ps header format")
1✔
137
    for line in ps_out.split("\n")[1:]:
1✔
138
        # COMMAND is the last column and may contain spaces
139
        parts = line.split(None, 10)
1✔
140
        if len(parts) < 11:
1✔
141
            continue
1✔
142
        yield ProcessInfo(int(parts[1]), parts[10].strip(), parts[0] == current_user)
1✔
143

144

145
def is_process_running(exename, require_same_user):
2✔
146
    """Check whether exename is running"""
147
    ci = os.name == 'nt'  # case-insensitive on Windows
2✔
148
    if ci:
2✔
149
        exename = exename.lower()
1✔
150
    for proc in enumerate_processes():
2✔
151
        name = proc.name.lower() if ci else proc.name
2✔
152
        if name == exename and (not require_same_user or proc.same_user):
2✔
153
            return True
2✔
154
    return False
2✔
155

156

157
def terminate_process(exename, require_same_user):
2✔
158
    """Terminate processes matching exename. Returns list of affected PIDs."""
159
    ci = os.name == 'nt'
×
160
    if ci:
×
161
        exename = exename.lower()
×
162
    terminated = []
×
163
    for proc in enumerate_processes():
×
164
        name = proc.name.lower() if ci else proc.name
×
165
        if name == exename and (not require_same_user or proc.same_user):
×
166
            if proc.pid in terminated:
×
167
                continue
×
168
            try:
×
169
                if IS_WINDOWS:
×
170
                    psutil.Process(proc.pid).kill()
×
171
                else:
172
                    os.kill(proc.pid, signal.SIGTERM)
×
173
                terminated.append(proc.pid)
×
174
            except (ProcessLookupError, PermissionError, OSError):
×
175
                continue
×
176
    return terminated
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc