#!/usr/bin/env python3 # print('Starting nohang_notify_helper') def decoder(string): """ """ decoded = '' for i in string.split(':'): decoded += chr(int(i)) return decoded def write(path, string): """ """ with open(path, 'w') as f: f.write(string) 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() 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 if user == 'USER=root': return None 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'): 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 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) _, uid, debug, title, body = argv uid = uid.partition('--euid=')[2] debug = debug.partition('--debug=')[2] if debug == 'True': debug = True else: debug = False title = title.partition('--title=')[2] body = decoder(body.partition('--body=')[2]) if len(argv) != 5: print('nohang_notify_helper: invalid input') exit(1) 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 = 10 else: wait_time = 2 if debug: print('nohang_notify_helper: wait_time:', wait_time, 'sec') if uid != '0': cmd = ['notify-send', '--icon=dialog-warning', title, body] if debug: print('nohang_notify_helper: run cmd:', cmd) with Popen(cmd) as proc: try: proc.wait(timeout=wait_time) except TimeoutExpired: proc.kill() if debug: print('nohang_notify_helper: TimeoutExpired') exit() display_env = 'DISPLAY=' dbus_env = 'DBUS_SESSION_BUS_ADDRESS=' user_env = 'USER=' 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: if debug: print('Send a GUI notification:\n ', 'title: ', [title], '\n body: ', [body], '\n user/env:', 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: if debug: print( 'Not send GUI notification: [', title, body, ']. Nobody logged-in with GUI. Nothing to do.')