#!/usr/bin/env python3 # print('Starting nohang_notify_helper') def write(path, string): """ """ with open(path, 'w') as f: f.write(string) try: write('/proc/self/oom_score_adj', '0') except Exception: pass try: from os import listdir, path, remove from subprocess import Popen, TimeoutExpired from sys import argv except OSError: exit(1) def rline1(path): """read 1st line from path.""" try: with open(path) as f: for line in f: return line except OSError: exit(1) def rfile(path): """read file.""" with open(path) as f: return f.read() with open('/proc/meminfo') as f: for line in f: if line.startswith('SwapTotal'): swap_total = int(line.split(':')[1][:-4]) if swap_total > 0: wait_time = 5 else: wait_time = 1 print('nohang_notify_helper: wait_time:', wait_time) # print(argv) # print(len(argv)) split_by = '#' * 16 uid = argv[2] t000 = argv[4] display_env = 'DISPLAY=' dbus_env = 'DBUS_SESSION_BUS_ADDRESS=' user_env = 'USER=' path_to_cache = '/dev/shm/nohang_notify_cache_uid{}_time{}'.format( uid, t000 ) try: title, body = rfile(path_to_cache).split(split_by) except FileNotFoundError: print('nohang_notify_helper: FileNotFoundError') exit(1) remove(path_to_cache) def re_pid_environ(pid): """ read environ of 1 process returns tuple with USER, DBUS, DISPLAY like follow: ('user', 'DISPLAY=:0', 'DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus') returns None if these vars is not in /proc/[pid]/environ """ try: env = str(rline1('/proc/' + pid + '/environ')) if display_env in env and dbus_env in env and user_env in env: env_list = env.split('\x00') # iterating over a list of process environment variables for i in env_list: if i.startswith(user_env): user = i continue if i.startswith(display_env): display = i[:10] continue if i.startswith(dbus_env): dbus = i continue if i.startswith('HOME='): # exclude Display Manager's user if i.startswith('HOME=/var') or i.startswith('HOME=/root'): return None try: env = user.partition('USER=')[2], display, dbus except UnboundLocalError: # print('notify helper: UnboundLocalError') return None return env except FileNotFoundError: # print('notify helper: FileNotFoundError') return None except ProcessLookupError: # print('notify helper: ProcessLookupError') return None def root_notify_env(): """return set(user, display, dbus)""" unsorted_envs_list = [] # iterates over processes, find processes with suitable env for pid in listdir('/proc'): if path.exists('/proc/' + pid + '/exe') is True: one_env = re_pid_environ(pid) unsorted_envs_list.append(one_env) env = set(unsorted_envs_list) env.discard(None) # deduplicate dbus new_env = [] end = [] for i in env: key = i[0] + i[1] if key not in end: end.append(key) new_env.append(i) else: continue return new_env list_with_envs = root_notify_env() list_len = len(list_with_envs) # if somebody logged in with GUI if list_len > 0: for i in list_with_envs: print('Send GUI notification:', title, body, i) # iterating over logged-in users for i in list_with_envs: username, display_env, dbus_env = i[0], i[1], i[2] display_tuple = display_env.partition('=') dbus_tuple = dbus_env.partition('=') display_value = display_tuple[2] dbus_value = dbus_tuple[2] try: with Popen([ 'sudo', '-u', username, 'env', 'DISPLAY=' + display_value, 'DBUS_SESSION_BUS_ADDRESS=' + dbus_value, 'notify-send', '--icon=dialog-warning', title, body ]) as proc: try: proc.wait(timeout=wait_time) except TimeoutExpired: proc.kill() print('TimeoutExpired: notify user: ' + username) except BlockingIOError: print('nohang_notify_helper: BlockingIOError') except OSError: print('nohang_notify_helper: OSError') except Exception: print('nohang_notify_helper: CANNOT SPAWN NOTIFY-SEND PROCESS') else: print( 'Not send GUI notification: [', title, body, ']. Nobody logged-in with GUI. Nothing to do.')