diff options
Diffstat (limited to 'python/vyos/util.py')
-rw-r--r-- | python/vyos/util.py | 205 |
1 files changed, 188 insertions, 17 deletions
diff --git a/python/vyos/util.py b/python/vyos/util.py index 67a602f7a..291ce64ea 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -15,15 +15,96 @@ import os import re -import getpass -import grp -import time -import subprocess import sys +from subprocess import Popen, PIPE, STDOUT, DEVNULL + +def debug(flag): + # this is to force all new flags to be registered here so that + # they can be documented: + # - developer: the code will drop into PBD on un-handled exception + # - ifconfig: prints command and sysfs access on stdout for interface + if flag not in ['developer', 'ifconfig']: + return '' + return flag if os.path.isfile(f'/tmp/vyos.{flag}.debug') else '' + + +def debug_msg(message, section=''): + if debug(section): + print(f'DEBUG/{section:<6} {message}') + + +def popen(command, section='', shell=None, input=None, timeout=None, env=None, + universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None): + """ popen does not raise, returns the output and error code of command """ + use_shell = shell + if shell is None: + use_shell = True if ' ' in command else False + p = Popen( + command, + stdout=stdout, stderr=stderr, + env=env, shell=use_shell, + universal_newlines=universal_newlines, + ) + tmp = p.communicate(input, timeout)[0].strip() + debug_msg(f"cmd '{command}'", section) + decoded = tmp.decode(decode) if decode else tmp.decode() + if decoded: + debug_msg(f"returned:\n{decoded}", section) + return decoded, p.returncode + + +def run(command, section='', shell=None, input=None, timeout=None, env=None, + universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None): + """ does not raise exception on error, returns error code """ + _, code = popen( + command, section, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + universal_newlines=universal_newlines, + decode=decode, + ) + return code + + +def cmd(command, section='', shell=None, input=None, timeout=None, env=None, + universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None, + raising=None, message=''): + """ does raise exception, returns output of command """ + decoded, code = popen( + command, section, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + universal_newlines=universal_newlines, + decode=decode, + ) + if code != 0: + feedback = message + '\n' if message else '' + feedback += f'failed to run command: {command}\n' + feedback += f'returned: {decoded}\n' + feedback += f'exit code: {code}' + if raising is None: + # error code can be recovered with .errno + raise OSError(code, feedback) + else: + raise raising(feedback) + return decoded -import psutil -import vyos.defaults +def call(command, section='', shell=None, input=None, timeout=None, env=None, + universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None): + """ does not raise exception on error, returns error code, print output """ + out, code = popen( + command, section, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + universal_newlines=universal_newlines, + decode=decode, + ) + print(out) + return code def read_file(path): @@ -32,6 +113,36 @@ def read_file(path): data = f.read().strip() return data + +def chown(path, user, group): + """ change file/directory owner """ + from pwd import getpwnam + from grp import getgrnam + + if os.path.exists(path): + uid = getpwnam(user).pw_uid + gid = getgrnam(group).gr_gid + os.chown(path, uid, gid) + +def chmod_750(path): + """ make file/directory only executable to user and group """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP + + if os.path.exists(path): + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP + os.chmod(path, bitmask) + + +def chmod_x(path): + """ make file executable """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH + + if os.path.exists(path): + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ + S_IROTH | S_IXOTH + os.chmod(path, bitmask) + + def colon_separated_to_dict(data_string, uniquekeys=False): """ Converts a string containing newline-separated entries of colon-separated key-value pairs into a dict. @@ -80,11 +191,16 @@ def colon_separated_to_dict(data_string, uniquekeys=False): return data + def process_running(pid_file): """ Checks if a process with PID in pid_file is running """ + from psutil import pid_exists + if not os.path.isfile(pid_file): + return False with open(pid_file, 'r') as f: pid = f.read().strip() - return psutil.pid_exists(int(pid)) + return pid_exists(int(pid)) + def seconds_to_human(s, separator=""): """ Converts number of seconds passed to a human-readable @@ -125,10 +241,15 @@ def seconds_to_human(s, separator=""): return result + def get_cfg_group_id(): - group_data = grp.getgrnam(vyos.defaults.cfg_group) + from grp import getgrnam + from vyos.defaults import cfg_group + + group_data = getgrnam(cfg_group) return group_data.gr_gid + def file_is_persistent(path): if not re.match(r'^(/config|/opt/vyatta/etc/config)', os.path.dirname(path)): warning = "Warning: file {0} is outside the /config directory\n".format(path) @@ -137,6 +258,7 @@ def file_is_persistent(path): else: return (True, None) + def commit_in_progress(): """ Not to be used in normal op mode scripts! """ @@ -154,29 +276,34 @@ def commit_in_progress(): # Since this will be used in scripts that modify the config outside of the CLI # framework, those knowingly have root permissions. # For everything else, we add a safeguard. - id = subprocess.check_output(['/usr/bin/id', '-u']).decode().strip() - if id != '0': + from psutil import process_iter, NoSuchProcess + from vyos.defaults import commit_lock + + idu = cmd('/usr/bin/id -u') + if idu != '0': raise OSError("This functions needs root permissions to return correct results") - for proc in psutil.process_iter(): + for proc in process_iter(): try: files = proc.open_files() if files: for f in files: - if f.path == vyos.defaults.commit_lock: + if f.path == commit_lock: return True - except psutil.NoSuchProcess as err: + except NoSuchProcess as err: # Process died before we could examine it pass # Default case return False + def wait_for_commit_lock(): """ Not to be used in normal op mode scripts! """ - + from time import sleep # Very synchronous approach to multiprocessing while commit_in_progress(): - time.sleep(1) + sleep(1) + def ask_yes_no(question, default=False) -> bool: """Ask a yes/no question via input() and return their answer.""" @@ -196,6 +323,50 @@ def ask_yes_no(question, default=False) -> bool: def is_admin() -> bool: """Look if current user is in sudo group""" - current_user = getpass.getuser() - (_, _, _, admin_group_members) = grp.getgrnam('sudo') + from getpass import getuser + from grp import getgrnam + current_user = getuser() + (_, _, _, admin_group_members) = getgrnam('sudo') return current_user in admin_group_members + + +def mac2eui64(mac, prefix=None): + """ + Convert a MAC address to a EUI64 address or, with prefix provided, a full + IPv6 address. + Thankfully copied from https://gist.github.com/wido/f5e32576bb57b5cc6f934e177a37a0d3 + """ + from ipaddress import ip_network + # http://tools.ietf.org/html/rfc4291#section-2.5.1 + eui64 = re.sub(r'[.:-]', '', mac).lower() + eui64 = eui64[0:6] + 'fffe' + eui64[6:] + eui64 = hex(int(eui64[0:2], 16) ^ 2)[2:].zfill(2) + eui64[2:] + + if prefix is None: + return ':'.join(re.findall(r'.{4}', eui64)) + else: + try: + net = ip_network(prefix, strict=False) + euil = int('0x{0}'.format(eui64), 16) + return str(net[euil]) + except: # pylint: disable=bare-except + return + +def is_bridge_member(interface): + """ + Checks if passed interfaces is part of a bridge device or not. + + Returns a tuple: + False, None -> Not part of a bridge + True, bridge-name -> If it is assigned to a bridge + """ + from vyos.config import Config + c = Config() + base = ['interfaces', 'bridge'] + for bridge in c.list_nodes(base): + members = c.list_nodes(base + [bridge, 'member', 'interface']) + if interface in members: + return (True, bridge) + + return False, None + |