Skip to content

Commit e4fd03e

Browse files
author
Thomas Desveaux
committed
check: bulletproof error handling in process checks
1 parent 9b2c737 commit e4fd03e

File tree

1 file changed

+68
-32
lines changed

1 file changed

+68
-32
lines changed

nimp/base_commands/check.py

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -201,68 +201,91 @@ def _run_check(self, env: NimpEnvironment):
201201
logging.debug("\t%s (%s) %s", ignored_process.exe(), ignored_process.pid, ignored_process.cmdline())
202202

203203
for process in psutil.process_iter():
204+
if not process.is_running():
205+
continue
206+
204207
logging.debug("Checking process %d", process.pid)
205208
if process.pid in ignore_process_ids:
206209
logging.debug("[Process(%d)] ignore process (self, parent or child)", process.pid)
207210
continue
208211

209212
if current_user is not None:
210-
process_user = None
211-
try:
212-
process_user = process.username()
213-
except psutil.AccessDenied:
214-
logging.debug(
215-
"[Process(%d)] Failed to retrieve process user",
216-
process.pid,
217-
)
218-
continue
219-
220-
if current_user != process_user:
221-
logging.debug(
222-
"[Process(%d)] ignore process from other user (self: %s, process user: %s)",
223-
process.pid,
224-
current_user,
225-
process_user,
226-
)
213+
if not _Processes._process_owned_by_user(process, current_user):
227214
continue
228215

229216
checked_processes_count += 1
230217
if not _Processes._process_matches_filters(process, env.filters):
231218
continue
232219

233-
process_executable_path = process.exe()
234-
process_basename = os.path.basename(process_executable_path)
235-
if any(p.match(process_basename) for p in _Processes.PROCESS_IGNORE_PATTERNS):
236-
logging.info('[Process(%d)] process (%s) will be kept alive', process.pid, process_executable_path)
220+
try:
221+
process_exe = process.exe()
222+
except psutil.Error as exc:
223+
logging.debug('[Process(%d)] failed to retrieve process executable', process.pid, exc_info=exc)
224+
process_exe = "[UNKOWN]"
225+
226+
if _Processes._should_ignore_process(process):
227+
logging.info('[Process(%d)] process (%s) will be kept alive', process.pid, process_exe)
237228
continue
238229

239230
problematic_processes.append(process)
240-
logging.warning('[Process(%d)] Found problematic process (%s)', process.pid, process_executable_path)
241-
if (parent_process := process.parent()) is not None:
242-
logging.warning('\tParent is %s (%s)', parent_process.pid, parent_process.exe())
231+
logging.warning('[Process(%d)] Found problematic process (%s)', process.pid, process_exe)
232+
try:
233+
if (parent_process := process.parent()) is not None:
234+
logging.warning('\tParent is %s (%s)', parent_process.pid, parent_process.exe())
235+
except psutil.Error as exc:
236+
logging.debug(
237+
'[Process(%d)] failed to get parent process information for process "%s"',
238+
process.pid, process_exe, exc_info=exc
239+
)
243240

244241
logging.info('%d processes checked.', checked_processes_count)
245242
if not problematic_processes:
246243
# no problematic processes running, nothing to do.
247244
return True
248245

246+
sleep_time = 5.0
249247
if not env.kill:
250248
# Wait a bit, give a chance to problematic processes to end,
251249
# even if not killed
252-
sleep_time = 5.0
250+
253251
logging.debug("Wait %.2fs. Giving a chance to processes for a natural exit", sleep_time)
254252
time.sleep(sleep_time)
255253
else:
256254
for p in problematic_processes:
257-
logging.info('Requesting process %s termination', p.pid)
258-
p.terminate()
259-
_, alive = psutil.wait_procs(problematic_processes, timeout=5)
255+
if p.is_running():
256+
logging.info('Requesting process %s termination', p.pid)
257+
p.terminate()
258+
_, alive = psutil.wait_procs(problematic_processes, timeout=sleep_time)
260259
for p in alive:
261-
logging.info('Process %s not terminated. Send kill.', p.pid)
262-
p.kill()
260+
if p.is_running():
261+
logging.info('Process %s not terminated. Send kill.', p.pid)
262+
p.kill()
263263

264264
return False
265265

266+
@staticmethod
267+
def _process_owned_by_user(process: psutil.Process, username: str):
268+
try:
269+
process_user = process.username()
270+
except psutil.Error as exception:
271+
logging.debug(
272+
"[Process(%d)] Failed to retrieve process user",
273+
process.pid,
274+
exc_info=exception,
275+
)
276+
return False
277+
278+
is_same_user = username == process_user
279+
280+
logging.debug(
281+
"[Process(%d)] ignore process from other user (self: %s, process user: %s)",
282+
process.pid,
283+
username,
284+
process_user,
285+
)
286+
287+
return is_same_user
288+
266289
@staticmethod
267290
def _process_matches_filters(process: psutil.Process, filters: list[str]) -> bool:
268291
"""Returns True if the process should be filtered out"""
@@ -293,14 +316,27 @@ def _process_matches_filters(process: psutil.Process, filters: list[str]) -> boo
293316
popen_file.path,
294317
)
295318
return True
296-
except psutil.AccessDenied as exc:
297-
logging.debug("[Process(%d)] Access Denied!", process.pid, exc_info=exc)
319+
except psutil.Error as exc:
320+
logging.debug("[Process(%d)] Error!", process.pid, exc_info=exc)
298321
# failed to access a property of the process,
299322
# assume it does not match to be safe
300323
return False
301324

302325
return False
303326

327+
@staticmethod
328+
def _should_ignore_process(process: psutil.Process) -> bool:
329+
try:
330+
process_executable_path = process.exe()
331+
process_basename = os.path.basename(process_executable_path)
332+
except psutil.Error:
333+
logging.debug(
334+
"[Process(%d)] failed to retrieve process exe/basename",
335+
process.pid,
336+
)
337+
return True
338+
return any(p.match(process_basename) for p in _Processes.PROCESS_IGNORE_PATTERNS)
339+
304340

305341
class _Disks(CheckCommand):
306342
def __init__(self):

0 commit comments

Comments
 (0)