summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/airbag.py169
-rw-r--r--python/vyos/remote.py29
-rw-r--r--python/vyos/util.py21
-rw-r--r--python/vyos/version.py15
4 files changed, 214 insertions, 20 deletions
diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py
new file mode 100644
index 000000000..664974d5f
--- /dev/null
+++ b/python/vyos/airbag.py
@@ -0,0 +1,169 @@
+# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import logging
+import logging.handlers
+from datetime import datetime
+
+from vyos.config import Config
+from vyos.version import get_version
+from vyos.util import run
+from vyos.util import debug
+
+
+# we allow to disable the extra logging
+DISABLE = False
+
+
+# emulate a file object
+class _IO(object):
+ def __init__(self, std, log):
+ self.std = std
+ self.log = log
+
+ def write(self, message):
+ self.std.write(message)
+ if DISABLE:
+ return
+ for line in message.split('\n'):
+ s = line.rstrip()
+ if s:
+ self.log(s)
+
+ def flush(self):
+ self.std.flush()
+
+ def close(self):
+ pass
+
+
+# The function which will be used to report information
+# to users when an exception is unhandled
+def bug_report(dtype, value, trace):
+ from traceback import format_exception
+
+ sys.stdout.flush()
+ sys.stderr.flush()
+
+ information = {
+ 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ 'version': get_version(),
+ 'trace': format_exception(dtype, value, trace),
+ 'instructions': COMMUNITY if 'rolling' in get_version() else SUPPORTED,
+ }
+
+ sys.stdout.write(INTRO.format(**information))
+ sys.stdout.flush()
+
+ sys.stderr.write(FAULT.format(**information))
+ sys.stderr.flush()
+
+
+# define an exception handler to be run when an exception
+# reach the end of __main__ and was not intercepted
+def intercepter(dtype, value, trace):
+ bug_report(dtype, value, trace)
+ # debug returns either '' or 'developer' if debuging is enabled
+ if debug('developer'):
+ import pdb
+ pdb.pm()
+
+
+def InterceptingLogger(address, _singleton=[False]):
+ skip = _singleton.pop()
+ _singleton.append(True)
+ if skip:
+ return
+
+ logger = logging.getLogger('VyOS')
+ logger.setLevel(logging.DEBUG)
+ handler = logging.handlers.SysLogHandler(address='/dev/log', facility='syslog')
+ logger.addHandler(handler)
+
+ # log to syslog any message sent to stderr
+ sys.stderr = _IO(sys.stderr, logger.critical)
+
+
+# lists as default arguments in function is normally dangerous
+# as they will keep any modification performed, unless this is
+# what you want to do (in that case to only run the code once)
+def InterceptingException(excepthook,_singleton=[False]):
+ skip = _singleton.pop()
+ _singleton.append(True)
+ if skip:
+ return
+
+ # install the handler to replace the default behaviour
+ # which just prints the exception trace on screen
+ sys.excepthook = excepthook
+
+
+# Do not attempt the extra logging for operational commands
+try:
+ # This fails during boot
+ insession = Config().in_session()
+except:
+ # we save info on boot to help debugging
+ insession = True
+
+
+# Installing the interception, it currently does not work when
+# running testing so we are checking that we are on the router
+# as otherwise it prevents dpkg-buildpackage to work
+if get_version() and insession:
+ InterceptingLogger('/run/systemd/journal/dev-log')
+ InterceptingException(intercepter)
+
+
+# Messages to print
+
+FAULT = """\
+Date: {date}
+VyOS image: {version}
+
+{trace}
+"""
+
+INTRO = """\
+VyOS had an issue completing a command.
+
+We are sorry that you encountered a problem with VyOS.
+There are a few things you can do to help us (and yourself):
+{instructions}
+
+PLEASE, when reporting, do include as much information as you can:
+- do not obfuscate any data (feel free to send us a private communication with
+ the extra information if your business policy is strict on information sharing)
+- and include all the information presented below
+
+"""
+
+COMMUNITY = """\
+- Make sure you are running the latest version of the code available at
+ https://downloads.vyos.io/rolling/current/amd64/vyos-rolling-latest.iso
+- Consult the forum to see how to handle this issue
+ https://forum.vyos.io
+- Join our community on slack where our users exchange help and advice
+ https://vyos.slack.com
+""".strip()
+
+SUPPORTED = """\
+- Make sure you are running the latest stable version of VyOS
+ the code is available at https://downloads.vyos.io/?dir=release/current
+- Contact us on our online help desk
+ https://support.vyos.io/
+""".strip()
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index f0bf41cd4..f8a21f068 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -17,8 +17,7 @@ import sys
import os
import re
import fileinput
-
-from vyos.util import cmd, DEVNULL
+import subprocess
def check_and_add_host_key(host_name):
@@ -34,8 +33,10 @@ def check_and_add_host_key(host_name):
keyscan_cmd = 'ssh-keyscan -t rsa {} 2>/dev/null'.format(host_name)
try:
- host_key = cmd(keyscan_cmd, stderr=DEVNULL, universal_newlines=True)
- except OSError:
+ host_key = subprocess.check_output(keyscan_cmd, shell=True,
+ stderr=subprocess.DEVNULL,
+ universal_newlines=True)
+ except subprocess.CalledProcessError as err:
sys.exit("Can not get RSA host key")
# libssh2 (jessie; stretch) does not recognize ec host keys, and curl
@@ -63,8 +64,10 @@ def check_and_add_host_key(host_name):
fingerprint_cmd = 'ssh-keygen -lf /dev/stdin <<< "{}"'.format(host_key)
try:
- fingerprint = cmd(fingerprint_cmd, stderr=DEVNULL, universal_newlines=True)
- except OSError:
+ fingerprint = subprocess.check_output(fingerprint_cmd, shell=True,
+ stderr=subprocess.DEVNULL,
+ universal_newlines=True)
+ except subprocess.CalledProcessError as err:
sys.exit("Can not get RSA host key fingerprint.")
print("RSA host key fingerprint is {}".format(fingerprint.split()[1]))
@@ -125,8 +128,9 @@ def get_remote_config(remote_file):
# Try header first, and look for 'OK' or 'Moved' codes:
curl_cmd = 'curl {0} -q -I {1}'.format(redirect_opt, remote_file)
try:
- curl_output = cmd(curl_cmd, shell=True, universal_newlines=True)
- except OSError:
+ curl_output = subprocess.check_output(curl_cmd, shell=True,
+ universal_newlines=True)
+ except subprocess.CalledProcessError:
sys.exit(1)
return_vals = re.findall(r'^HTTP\/\d+\.?\d\s+(\d+)\s+(.*)$',
@@ -142,6 +146,9 @@ def get_remote_config(remote_file):
curl_cmd = 'curl {0} -# {1}'.format(redirect_opt, remote_file)
try:
- return cmd(curl_cmd, universal_newlines=True)
- except OSError:
- return None
+ config_file = subprocess.check_output(curl_cmd, shell=True,
+ universal_newlines=True)
+ except subprocess.CalledProcessError:
+ config_file = None
+
+ return config_file
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 16cfae92d..c827425ee 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -19,6 +19,12 @@ 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 False
return flag if os.path.isfile(f'/tmp/vyos.{flag}.debug') else ''
@@ -93,16 +99,25 @@ def read_file(path):
return data
-def chown_file(path, user, group):
- """ change file owner """
+def chown(path, user, group):
+ """ change file/directory owner """
from pwd import getpwnam
from grp import getgrnam
- if os.path.isfile(path):
+ 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
diff --git a/python/vyos/version.py b/python/vyos/version.py
index 383efbc1e..d51a940d6 100644
--- a/python/vyos/version.py
+++ b/python/vyos/version.py
@@ -44,7 +44,7 @@ def get_version_data(file=version_file):
file (str): path to the version file
Returns:
- dict: version data
+ dict: version data, if it can not be found and empty dict
The optional ``file`` argument comes in handy in upgrade scripts
that need to retrieve information from images other than the running image.
@@ -52,17 +52,20 @@ def get_version_data(file=version_file):
is an implementation detail and may change in the future, while the interface
of this module will stay the same.
"""
- with open(file, 'r') as f:
- version_data = json.load(f)
- return version_data
+ try:
+ with open(file, 'r') as f:
+ version_data = json.load(f)
+ return version_data
+ except FileNotFoundError:
+ return {}
def get_version(file=None):
"""
- Get the version number
+ Get the version number, or an empty string if it could not be determined
"""
version_data = None
if file:
version_data = get_version_data(file=file)
else:
version_data = get_version_data()
- return version_data["version"]
+ return version_data.get('version','')