summaryrefslogtreecommitdiff
path: root/python/vyos/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/util.py')
-rw-r--r--python/vyos/util.py205
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
+