diff --git a/python/vyos/config.py b/python/vyos/config.py
index e5963c19a..5bd8fb072 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -1,467 +1,461 @@
 # Copyright 2017, 2019 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/>.
 
 """
 A library for reading VyOS running config data.
 
 This library is used internally by all config scripts of VyOS,
 but its API should be considered stable and safe to use
 in user scripts.
 
 Note that this module will not work outside VyOS.
 
 Node taxonomy
 #############
 
 There are multiple types of config tree nodes in VyOS, each requires
 its own set of operations.
 
 *Leaf nodes* (such as "address" in interfaces) can have values, but cannot
 have children. 
 Leaf nodes can have one value, multiple values, or no values at all.
 
 For example, "system host-name" is a single-value leaf node,
 "system name-server" is a multi-value leaf node (commonly abbreviated "multi node"),
 and "system ip disable-forwarding" is a valueless leaf node.
 
 Non-leaf nodes cannot have values, but they can have child nodes. They are divided into
 two classes depending on whether the names of their children are fixed or not.
 For example, under "system", the names of all valid child nodes are predefined
 ("login", "name-server" etc.).
 
 To the contrary, children of the "system task-scheduler task" node can have arbitrary names.
 Such nodes are called *tag nodes*. This terminology is confusing but we keep using it for lack
 of a better word. No one remembers if the "tag" in "task Foo" is "task" or "Foo",
 but the distinction is irrelevant in practice.
 
 Configuration modes
 ###################
 
 VyOS has two distinct modes: operational mode and configuration mode. When a user logins,
 the CLI is in the operational mode. In this mode, only the running (effective) config is accessible for reading.
 
 When a user enters the "configure" command, a configuration session is setup. Every config session
 has its *proposed* (or *session*) config built on top of the current running config. When changes are commited, if commit succeeds,
 the proposed config is merged into the running config.
 
 In configuration mode, "base" functions like `exists`, `return_value` return values from the session config,
 while functions prefixed "effective" return values from the running config.
 
 In operational mode, all functions return values from the running config.
 
 """
 
 import os
 import re
 import json
 import subprocess
 
 import vyos.configtree
-import vyos.util
 
 
 class VyOSError(Exception):
     """
     Raised on config access errors, most commonly if the type of a config tree node
     in the system does not match the type of operation.
 
     """
     pass
 
 
 class Config(object):
     """
     The class of config access objects.
 
     Internally, in the current implementation, this object is *almost* stateless,
     the only state it keeps is relative *config path* for convenient access to config
     subtrees.
     """
     def __init__(self, session_env=None):
         self._cli_shell_api = "/bin/cli-shell-api"
         self._level = []
         if session_env:
             self.__session_env = session_env
         else:
             self.__session_env = None
 
         # Running config can be obtained either from op or conf mode, it always succeeds
         # (if config system is initialized at all).
         if os.path.isfile('/tmp/vyos-config-status'):
             running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', 'showConfig'])
         else:
             with open('/opt/vyatta/etc/config/config.boot') as f:
                 running_config_text = f.read()
 
         # Session config ("active") only exists in conf mode.
         # In op mode, we'll just use the same running config for both active and session configs.
         if self.in_session():
             session_config_text = self._run([self._cli_shell_api, '--show-working-only', '--show-show-defaults', 'showConfig'])
         else:
             session_config_text = running_config_text
 
-        # The output of showConfig does not escape backslashes, as is expected
-        # by ConfigTree().
-        session_config_text = vyos.util.escape_backslash(session_config_text)
-        running_config_text = vyos.util.escape_backslash(running_config_text)
-
         self._session_config = vyos.configtree.ConfigTree(session_config_text)
         self._running_config = vyos.configtree.ConfigTree(running_config_text)
 
     def _make_command(self, op, path):
         args = path.split()
         cmd = [self._cli_shell_api, op] + args
         return cmd
 
     def _make_path(self, path):
         # Backwards-compatibility stuff: original implementation used string paths
         # libvyosconfig paths are lists, but since node names cannot contain whitespace,
         # splitting at whitespace is reasonably safe.
         # It may cause problems with exists() when it's used for checking values,
         # since values may contain whitespace.
         if isinstance(path, str):
             path = re.split(r'\s+', path)
         elif isinstance(path, list):
             pass
         else:
             raise TypeError("Path must be a whitespace-separated string or a list")
         return (self._level + path)
 
     def _run(self, cmd):
         if self.__session_env:
             p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env)
         else:
             p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
         out = p.stdout.read()
         p.wait()
         if p.returncode != 0:
             raise VyOSError()
         else:
             return out.decode('ascii')
 
     def set_level(self, path):
         """
         Set the *edit level*, that is, a relative config tree path.
         Once set, all operations will be relative to this path,
         for example, after ``set_level("system")``, calling
         ``exists("name-server")`` is equivalent to calling
         ``exists("system name-server"`` without ``set_level``.
 
         Args:
             path (str): relative config path
         """
         # Make sure there's always a space between default path (level)
         # and path supplied as method argument
         # XXX: for small strings in-place concatenation is not a problem
         if isinstance(path, str):
             self._level = re.split(r'\s+', path)
         elif isinstance(path, list):
             self._level = path
         else:
             raise TypeError("Level path must be either a whitespace-separated string or a list")
 
     def get_level(self):
         """
         Gets the current edit level.
 
         Returns:
             str: current edit level
         """
         return(self._level)
 
     def exists(self, path):
         """
         Checks if a node with given path exists in the running or proposed config
 
         Returns:
             True if node exists, False otherwise
 
         Note:
             This function cannot be used outside a configuration sessions.
             In operational mode scripts, use ``exists_effective``.
         """
         if self._session_config.exists(self._make_path(path)):
             return True
         else:
             # libvyosconfig exists() works only for _nodes_, not _values_
             # libvyattacfg one also worked for values, so we emulate that case here
             path = re.split(r'\s+', path)
             path_without_value = path[:-1]
             path_str = " ".join(path_without_value)
             try:
                 value = self._session_config.return_value(self._make_path(path_str))
                 return (value == path[-1])
             except vyos.configtree.ConfigTreeError:
                 # node doesn't exist at all
                 return False
 
     def session_changed(self):
         """
         Returns:
             True if the config session has uncommited changes, False otherwise.
         """
         try:
             self._run(self._make_command('sessionChanged', ''))
             return True
         except VyOSError:
             return False
 
     def in_session(self):
         """
         Returns:
             True if called from a configuration session, False otherwise.
         """
         try:
             self._run(self._make_command('inSession', ''))
             return True
         except VyOSError:
             return False
 
     def show_config(self, path=[], default=None):
         """
         Args:
             path (str list): Configuration tree path, or empty
             default (str): Default value to return
 
         Returns:
             str: working configuration
         """
         if isinstance(path, list):
             path = " ".join(path)
         try:
             out = self._run(self._make_command('showConfig', path))
             return out
         except VyOSError:
             return(default)
 
     def get_config_dict(self, path=[], effective=False):
         """
         Args: path (str list): Configuration tree path, can be empty
         Returns: a dict representation of the config
         """
         res = self.show_config(self._make_path(path))
         config_tree = vyos.configtree.ConfigTree(res)
         config_dict = json.loads(config_tree.to_json())
         return config_dict
 
     def is_multi(self, path):
         """
         Args:
             path (str): Configuration tree path
 
         Returns:
             True if a node can have multiple values, False otherwise.
 
         Note:
             It also returns False if node doesn't exist.
         """
         try:
             path = " ".join(self._level) + " " + path
             self._run(self._make_command('isMulti', path))
             return True
         except VyOSError:
             return False
 
     def is_tag(self, path):
         """
          Args:
             path (str): Configuration tree path
 
         Returns:
             True if a node is a tag node, False otherwise.
 
         Note:
             It also returns False if node doesn't exist.
         """
         try:
             path = " ".join(self._level) + " " + path
             self._run(self._make_command('isTag', path))
             return True
         except VyOSError:
             return False
 
     def is_leaf(self, path):
         """
          Args:
             path (str): Configuration tree path
 
         Returns:
             True if a node is a leaf node, False otherwise.
 
         Note:
             It also returns False if node doesn't exist.
         """
         try:
             path = " ".join(self._level) + " " + path
             self._run(self._make_command('isLeaf', path))
             return True
         except VyOSError:
             return False
 
     def return_value(self, path, default=None):
         """
         Retrieve a value of single-value leaf node in the running or proposed config
 
         Args:
             path (str): Configuration tree path
             default (str): Default value to return if node does not exist
 
         Returns:
             str: Node value, if it has any
             None: if node is valueless *or* if it doesn't exist
 
         Note:
             Due to the issue with treatment of valueless nodes by this function,
             valueless nodes should be checked with ``exists`` instead.
 
             This function cannot be used outside a configuration session.
             In operational mode scripts, use ``return_effective_value``.
         """
         try:
             value = self._session_config.return_value(self._make_path(path))
         except vyos.configtree.ConfigTreeError:
             value = None
 
         if not value:
             return(default)
         else:
             return(value)
 
     def return_values(self, path, default=[]):
         """
         Retrieve all values of a multi-value leaf node in the running or proposed config
 
         Args:
             path (str): Configuration tree path
 
         Returns:
             str list: Node values, if it has any
             []: if node does not exist
 
         Note:
             This function cannot be used outside a configuration session.
             In operational mode scripts, use ``return_effective_values``.
         """
         try:
             values = self._session_config.return_values(self._make_path(path))
         except vyos.configtree.ConfigTreeError:
             values = []
 
         if not values:
             return(default)
         else:
             return(values)
 
     def list_nodes(self, path, default=[]):
         """
         Retrieve names of all children of a tag node in the running or proposed config
 
         Args:
             path (str): Configuration tree path
 
         Returns:
             string list: child node names
 
         """
         try:
             nodes = self._session_config.list_nodes(self._make_path(path))
         except vyos.configtree.ConfigTreeError:
             nodes = []
 
         if not nodes:
             return(default)
         else:
             return(nodes)
 
     def exists_effective(self, path):
         """
         Check if a node exists in the running (effective) config
 
         Args:
             path (str): Configuration tree path
 
         Returns:
             True if node exists in the running config, False otherwise
 
         Note:
             This function is safe to use in operational mode. In configuration mode,
             it ignores uncommited changes.
         """
         return(self._running_config.exists(self._make_path(path)))
 
     def return_effective_value(self, path, default=None):
         """
         Retrieve a values of a single-value leaf node in a running (effective) config
 
         Args:
             path (str): Configuration tree path
             default (str): Default value to return if node does not exist
 
         Returns:
             str: Node value
         """
         try:
             value = self._running_config.return_value(self._make_path(path))
         except vyos.configtree.ConfigTreeError:
             value = None
 
         if not value:
             return(default)
         else:
             return(value)
 
 
     def return_effective_values(self, path, default=[]):
         """
         Retrieve all values of a multi-value node in a running (effective) config
 
         Args:
             path (str): Configuration tree path
 
         Returns:
             str list: A list of values
         """
         try:
             values = self._running_config.return_values(self._make_path(path))
         except vyos.configtree.ConfigTreeError:
             values = []
 
         if not values:
             return(default)
         else:
             return(values)
 
     def list_effective_nodes(self, path, default=[]):
         """
         Retrieve names of all children of a tag node in the running config
 
         Args:
             path (str): Configuration tree path
 
         Returns:
             str list: child node names
 
         Raises:
             VyOSError: if the node is not a tag node
         """
         try:
             nodes = self._running_config.list_nodes(self._make_path(path))
         except vyos.configtree.ConfigTreeError:
             nodes = []
 
         if not nodes:
             return(default)
         else:
             return(nodes)
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index 77cffe90b..0274f3573 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -1,324 +1,331 @@
 # configtree -- a standalone VyOS config file manipulation library (Python bindings)
 # Copyright (C) 2018 VyOS maintainers and contributors
 #
 # 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 
 import re
 import json
 
 from ctypes import cdll, c_char_p, c_void_p, c_int
 
 
+def escape_backslash(string: str) -> str:
+    """Escape single backslashes in string that are not in escape sequence"""
+    p = re.compile(r'(?<!\\)[\\](?!b|f|n|r|t|\\[^bfnrt])')
+    result = p.sub(r'\\\\', string)
+    return result
+
 def strip_comments(s):
     """ Split a config string into the config section and the trailing comments """
     INITIAL = 0
     IN_COMMENT = 1
 
     i = len(s) - 1
 
     state = INITIAL
 
     config_end = 0
 
     # Find the first character of the comments section at the end,
     # if it exists
     while (i >= 0):
         c = s[i]
 
         if (state == INITIAL) and re.match(r'\s', c):
             # Ignore whitespace
             if (i != 0):
                 i -= 1
             else:
                 config_end = 0
                 break
         elif (state == INITIAL) and not re.match(r'(\s|\/)', c):
             # Assume there are no (more) trailing comments,
             # this is an end of a node: either a brace of the last character
             # of a leaf node value
             config_end = i + 1
             break
         elif (state == INITIAL) and (c == '/'):
             # A comment begins, or it's a stray slash
             if (s[i-1] == '*'):
                 state = IN_COMMENT
                 i -= 2
             else:
                 raise ValueError("Invalid syntax: stray slash at character {0}".format(i + 1))
         elif (state == IN_COMMENT) and (c == '*'):
             # A comment ends here
             try:
                 if (s[i-1] == '/'):
                     state = INITIAL
                     i -= 2
             except:
                 raise ValueError("Invalid syntax: malformed commend end at character {0}".format(i + 1))
         elif (state == IN_COMMENT) and (c != '*'):
             # Ignore everything inside comments, including braces
             i -= 1
         else:
             # Shouldn't happen
             raise ValueError("Invalid syntax at character {0}: invalid character {1}".format(i + 1, c))
 
     return (s[0:config_end], s[config_end+1:])
 
 def check_path(path):
     # Necessary type checking
     if not isinstance(path, list):
         raise TypeError("Expected a list, got a {}".format(type(path)))
     else:
         pass
 
 
 class ConfigTreeError(Exception):
     pass
 
 
 class ConfigTree(object):
     def __init__(self, config_string, libpath='/usr/lib/libvyosconfig.so.0'):
         self.__config = None
         self.__lib = cdll.LoadLibrary(libpath)
 
         # Import functions
         self.__from_string = self.__lib.from_string
         self.__from_string.argtypes = [c_char_p]
         self.__from_string.restype = c_void_p
 
         self.__get_error = self.__lib.get_error
         self.__get_error.argtypes = []
         self.__get_error.restype = c_char_p
 
         self.__to_string = self.__lib.to_string
         self.__to_string.argtypes = [c_void_p]
         self.__to_string.restype = c_char_p
 
         self.__to_commands = self.__lib.to_commands
         self.__to_commands.argtypes = [c_void_p]
         self.__to_commands.restype = c_char_p
 
         self.__to_json = self.__lib.to_json
         self.__to_json.argtypes = [c_void_p]
         self.__to_json.restype = c_char_p
 
         self.__to_json_ast = self.__lib.to_json_ast
         self.__to_json_ast.argtypes = [c_void_p]
         self.__to_json_ast.restype = c_char_p
 
         self.__set_add_value = self.__lib.set_add_value
         self.__set_add_value.argtypes = [c_void_p, c_char_p, c_char_p]
         self.__set_add_value.restype = c_int
 
         self.__delete_value = self.__lib.delete_value
         self.__delete_value.argtypes = [c_void_p, c_char_p, c_char_p]
         self.__delete_value.restype = c_int
 
         self.__delete = self.__lib.delete_node
         self.__delete.argtypes = [c_void_p, c_char_p]
         self.__delete.restype = c_int
 
         self.__rename = self.__lib.rename_node
         self.__rename.argtypes = [c_void_p, c_char_p, c_char_p]
         self.__rename.restype = c_int
 
         self.__copy = self.__lib.copy_node
         self.__copy.argtypes = [c_void_p, c_char_p, c_char_p]
         self.__copy.restype = c_int
 
         self.__set_replace_value = self.__lib.set_replace_value
         self.__set_replace_value.argtypes = [c_void_p, c_char_p, c_char_p]
         self.__set_replace_value.restype = c_int
 
         self.__set_valueless = self.__lib.set_valueless
         self.__set_valueless.argtypes = [c_void_p, c_char_p]
         self.__set_valueless.restype = c_int
 
         self.__exists = self.__lib.exists
         self.__exists.argtypes = [c_void_p, c_char_p]
         self.__exists.restype = c_int
 
         self.__list_nodes = self.__lib.list_nodes
         self.__list_nodes.argtypes = [c_void_p, c_char_p]
         self.__list_nodes.restype = c_char_p
 
         self.__return_value = self.__lib.return_value
         self.__return_value.argtypes = [c_void_p, c_char_p]
         self.__return_value.restype = c_char_p
 
         self.__return_values = self.__lib.return_values
         self.__return_values.argtypes = [c_void_p, c_char_p]
         self.__return_values.restype = c_char_p
 
         self.__is_tag = self.__lib.is_tag
         self.__is_tag.argtypes = [c_void_p, c_char_p]
         self.__is_tag.restype = c_int
 
         self.__set_tag = self.__lib.set_tag
         self.__set_tag.argtypes = [c_void_p, c_char_p]
         self.__set_tag.restype = c_int
 
         self.__destroy = self.__lib.destroy
         self.__destroy.argtypes = [c_void_p]
 
         config_section, comments_section = strip_comments(config_string)
+        config_section = escape_backslash(config_section)
         config = self.__from_string(config_section.encode())
         if config is None:
             msg = self.__get_error().decode()
             raise ValueError("Failed to parse config: {0}".format(msg))
         else:
             self.__config = config
             self.__comments = comments_section
 
     def __del__(self):
         if self.__config is not None:
             self.__destroy(self.__config)
 
     def __str__(self):
         return self.to_string()
 
     def to_string(self):
         config_string = self.__to_string(self.__config).decode()
         config_string = "{0}\n{1}".format(config_string, self.__comments)
         return config_string
 
     def to_commands(self):
         return self.__to_commands(self.__config).decode()
 
     def to_json(self):
         return self.__to_json(self.__config).decode()
 
     def to_json_ast(self):
         return self.__to_json_ast(self.__config).decode()
 
     def set(self, path, value=None, replace=True):
         """Set new entry in VyOS configuration.
         path: configuration path e.g. 'system dns forwarding listen-address'
         value: value to be added to node, e.g. '172.18.254.201'
         replace: True: current occurance will be replaced
                  False: new value will be appended to current occurances - use
                  this for adding values to a multi node
         """
 
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         if value is None:
             self.__set_valueless(self.__config, path_str)
         else:
             if replace:
                 self.__set_replace_value(self.__config, path_str, str(value).encode())
             else:
                 self.__set_add_value(self.__config, path_str, str(value).encode())
 
     def delete(self, path):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         self.__delete(self.__config, path_str)
 
     def delete_value(self, path, value):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         self.__delete_value(self.__config, path_str, value.encode())
 
     def rename(self, path, new_name):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
         newname_str = new_name.encode()
 
         # Check if a node with intended new name already exists
         new_path = path[:-1] + [new_name]
         if self.exists(new_path):
             raise ConfigTreeError()
         res = self.__rename(self.__config, path_str, newname_str)
         if (res != 0):
             raise ConfigTreeError("Path [{}] doesn't exist".format(oldpath))
 
     def copy(self, old_path, new_path):
         check_path(old_path)
         check_path(new_path)
         oldpath_str = " ".join(map(str, old_path)).encode()
         newpath_str = " ".join(map(str, new_path)).encode()
 
         # Check if a node with intended new name already exists
         if self.exists(new_path):
             raise ConfigTreeError()
         res = self.__copy(self.__config, oldpath_str, newpath_str)
         if (res != 0):
             raise ConfigTreeError("Path [{}] doesn't exist".format(oldpath))
 
     def exists(self, path):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         res = self.__exists(self.__config, path_str)
         if (res == 0):
             return False
         else:
             return True
 
     def list_nodes(self, path):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         res_json = self.__list_nodes(self.__config, path_str).decode()
         res = json.loads(res_json)
 
         if res is None:
             raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
         else:
             return res
 
     def return_value(self, path):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         res_json = self.__return_value(self.__config, path_str).decode()
         res = json.loads(res_json)
 
         if res is None:
             raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
         else:
             return res
 
     def return_values(self, path):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         res_json = self.__return_values(self.__config, path_str).decode()
         res = json.loads(res_json)
 
         if res is None:
             raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
         else:
             return res
 
     def is_tag(self, path):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         res = self.__is_tag(self.__config, path_str)
         if (res >= 1):
             return True
         else:
             return False
 
     def set_tag(self, path):
         check_path(path)
         path_str = " ".join(map(str, path)).encode()
 
         res = self.__set_tag(self.__config, path_str)
         if (res == 0):
             return True
         else:
             raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
 
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 659a702fd..67a602f7a 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -1,207 +1,201 @@
 # Copyright 2019 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 re
 import getpass
 import grp
 import time
 import subprocess
 import sys
 
 import psutil
 
 import vyos.defaults
 
 
 def read_file(path):
     """ Read a file to string """
     with open(path, 'r') as f:
         data = f.read().strip()
     return data
 
 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.
 
         Such files are common in Linux /proc filesystem
 
     Args:
         data_string (str): data string
         uniquekeys (bool): whether to insist that keys are unique or not
 
     Returns: dict
 
     Raises:
         ValueError: if uniquekeys=True and the data string has
             duplicate keys.
 
     Note:
         If uniquekeys=True, then dict entries are always strings,
         otherwise they are always lists of strings.
     """
     key_value_re = re.compile('([^:]+)\s*\:\s*(.*)')
 
     data_raw = re.split('\n', data_string)
 
     data = {}
 
     for l in data_raw:
         l = l.strip()
         if l:
             match = re.match(key_value_re, l)
             if match:
                 key = match.groups()[0].strip()
                 value = match.groups()[1].strip()
             if key in data.keys():
                 if uniquekeys:
                     raise ValueError("Data string has duplicate keys: {0}".format(key))
                 else:
                     data[key].append(value)
             else:
                 if uniquekeys:
                     data[key] = value
                 else:
                     data[key] = [value]
         else:
             pass
 
     return data
 
 def process_running(pid_file):
     """ Checks if a process with PID in pid_file is running """
     with open(pid_file, 'r') as f:
         pid = f.read().strip()
     return psutil.pid_exists(int(pid))
 
 def seconds_to_human(s, separator=""):
     """ Converts number of seconds passed to a human-readable
     interval such as 1w4d18h35m59s
     """
     s = int(s)
 
     week = 60 * 60 * 24 * 7
     day = 60 * 60 * 24
     hour = 60 * 60
 
     remainder = 0
     result = ""
 
     weeks = s // week
     if weeks > 0:
         result = "{0}w".format(weeks)
         s = s % week
 
     days = s // day
     if days > 0:
         result = "{0}{1}{2}d".format(result, separator, days)
         s = s % day
 
     hours = s // hour
     if hours > 0:
         result = "{0}{1}{2}h".format(result, separator, hours)
         s = s % hour
 
     minutes = s // 60
     if minutes > 0:
         result = "{0}{1}{2}m".format(result, separator, minutes)
         s = s % 60
 
     seconds = s
     if seconds > 0:
         result = "{0}{1}{2}s".format(result, separator, seconds)
 
     return result
 
 def get_cfg_group_id():
     group_data = grp.getgrnam(vyos.defaults.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)
         warning += "It will not be automatically migrated to a new image on system update"
         return (False, warning)
     else:
         return (True, None)
 
 def commit_in_progress():
     """ Not to be used in normal op mode scripts! """
 
     # The CStore backend locks the config by opening a file
     # The file is not removed after commit, so just checking
     # if it exists is insufficient, we need to know if it's open by anyone
 
     # There are two ways to check if any other process keeps a file open.
     # The first one is to try opening it and see if the OS objects.
     # That's faster but prone to race conditions and can be intrusive.
     # The other one is to actually check if any process keeps it open.
     # It's non-intrusive but needs root permissions, else you can't check
     # processes of other users.
     #
     # 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':
         raise OSError("This functions needs root permissions to return correct results")
 
     for proc in psutil.process_iter():
         try:
             files = proc.open_files()
             if files:
                 for f in files:
                     if f.path == vyos.defaults.commit_lock:
                         return True
         except psutil.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! """
 
     # Very synchronous approach to multiprocessing
     while commit_in_progress():
         time.sleep(1)
 
 def ask_yes_no(question, default=False) -> bool:
     """Ask a yes/no question via input() and return their answer."""
     default_msg = "[Y/n]" if default else "[y/N]"
     while True:
         sys.stdout.write("%s %s " % (question, default_msg))
         c = input().lower()
         if c == '':
             return default
         elif c in ("y", "ye", "yes"):
             return True
         elif c in ("n", "no"):
             return False
         else:
             sys.stdout.write("Please respond with yes/y or no/n\n")
 
 
 def is_admin() -> bool:
     """Look if current user is in sudo group"""
     current_user = getpass.getuser()
     (_, _, _, admin_group_members) = grp.getgrnam('sudo')
     return current_user in admin_group_members
-
-def escape_backslash(string: str) -> str:
-    """Escape single backslashes in string that are not in escape sequence"""
-    p = re.compile(r'(?<!\\)[\\](?!b|f|n|r|t|\\[^bfnrt])')
-    result = p.sub(r'\\\\', string)
-    return result
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index ba684b553..7322e0c50 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -1,166 +1,164 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2019 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program 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 General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import exit
 from copy import deepcopy
 
-from vyos.configdict import list_diff
 from vyos.config import Config
 from vyos.ifconfig import GeneveIf, Interface
-from vyos.interfaces import get_type_of_interface
 from vyos import ConfigError
 from netifaces import interfaces
 
 default_config_data = {
     'address': [],
     'deleted': False,
     'description': '',
     'disable': False,
     'intf': '',
     'ip_arp_cache_tmo': 30,
     'ip_proxy_arp': 0,
     'mtu': 1500,
     'remote': '',
     'vni': ''
 }
 
 def get_config():
     geneve = deepcopy(default_config_data)
     conf = Config()
 
     # determine tagNode instance
     try:
         geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE']
     except KeyError as E:
         print("Interface not specified")
 
     # Check if interface has been removed
     if not conf.exists('interfaces geneve ' + geneve['intf']):
         geneve['deleted'] = True
         return geneve
 
     # set new configuration level
     conf.set_level('interfaces geneve ' + geneve['intf'])
 
     # retrieve configured interface addresses
     if conf.exists('address'):
         geneve['address'] = conf.return_values('address')
 
     # retrieve interface description
     if conf.exists('description'):
         geneve['description'] = conf.return_value('description')
 
     # Disable this interface
     if conf.exists('disable'):
         geneve['disable'] = True
 
     # ARP cache entry timeout in seconds
     if conf.exists('ip arp-cache-timeout'):
         geneve['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout'))
 
     # Enable proxy-arp on this interface
     if conf.exists('ip enable-proxy-arp'):
         geneve['ip_proxy_arp'] = 1
 
     # Maximum Transmission Unit (MTU)
     if conf.exists('mtu'):
         geneve['mtu'] = int(conf.return_value('mtu'))
 
     # Remote address of GENEVE tunnel
     if conf.exists('remote'):
         geneve['remote'] = conf.return_value('remote')
 
     # Virtual Network Identifier
     if conf.exists('vni'):
         geneve['vni'] = conf.return_value('vni')
 
     return geneve
 
 
 def verify(geneve):
     if geneve['deleted']:
         # bail out early
         return None
 
     if not geneve['remote']:
         raise ConfigError('GENEVE remote must be configured')
 
     if not geneve['vni']:
         raise ConfigError('GENEVE VNI must be configured')
 
     return None
 
 
 def generate(geneve):
     return None
 
 
 def apply(geneve):
     # Check if GENEVE interface already exists
     if geneve['intf'] in interfaces():
         v = GeneveIf(geneve['intf'])
         # GENEVE is super picky and the tunnel always needs to be recreated,
         # thus we can simply always delete it first.
         v.remove()
 
     if not geneve['deleted']:
         # GENEVE interface needs to be created on-block
         # instead of passing a ton of arguments, I just use a dict
         # that is managed by vyos.ifconfig
         conf = deepcopy(GeneveIf.get_config())
 
         # Assign GENEVE instance configuration parameters to config dict
         conf['vni'] = geneve['vni']
         conf['remote'] = geneve['remote']
 
         # Finally create the new interface
         v = GeneveIf(geneve['intf'], config=conf)
         # update interface description used e.g. by SNMP
         v.set_alias(geneve['description'])
         # Maximum Transfer Unit (MTU)
         v.set_mtu(geneve['mtu'])
 
         # configure ARP cache timeout in milliseconds
         v.set_arp_cache_tmo(geneve['ip_arp_cache_tmo'])
         # Enable proxy-arp on this interface
         v.set_proxy_arp(geneve['ip_proxy_arp'])
 
         # Configure interface address(es) - no need to implicitly delete the
         # old addresses as they have already been removed by deleting the
         # interface above
         for addr in geneve['address']:
             v.add_addr(addr)
 
         # As the bond interface is always disabled first when changing
         # parameters we will only re-enable the interface if it is not
         # administratively disabled
         if not geneve['disable']:
             v.set_state('up')
 
     return None
 
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 30ff1755d..c1fedc824 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -1,199 +1,197 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2019 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program 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 General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import exit
 from copy import deepcopy
 
-from vyos.configdict import list_diff
 from vyos.config import Config
 from vyos.ifconfig import VXLANIf, Interface
-from vyos.interfaces import get_type_of_interface
 from vyos import ConfigError
 from netifaces import interfaces
 
 default_config_data = {
     'address': [],
     'deleted': False,
     'description': '',
     'disable': False,
     'group': '',
     'intf': '',
     'ip_arp_cache_tmo': 30,
     'ip_proxy_arp': 0,
     'link': '',
     'mtu': 1450,
     'remote': '',
     'remote_port': 8472, # The Linux implementation of VXLAN pre-dates
                          # the IANA's selection of a standard destination port
     'vni': ''
 }
 
 def get_config():
     vxlan = deepcopy(default_config_data)
     conf = Config()
 
     # determine tagNode instance
     try:
         vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE']
     except KeyError as E:
         print("Interface not specified")
 
     # Check if interface has been removed
     if not conf.exists('interfaces vxlan ' + vxlan['intf']):
         vxlan['deleted'] = True
         return vxlan
 
     # set new configuration level
     conf.set_level('interfaces vxlan ' + vxlan['intf'])
 
     # retrieve configured interface addresses
     if conf.exists('address'):
         vxlan['address'] = conf.return_values('address')
 
     # retrieve interface description
     if conf.exists('description'):
         vxlan['description'] = conf.return_value('description')
 
     # Disable this interface
     if conf.exists('disable'):
         vxlan['disable'] = True
 
     # VXLAN multicast grou
     if conf.exists('group'):
         vxlan['group'] = conf.return_value('group')
 
     # ARP cache entry timeout in seconds
     if conf.exists('ip arp-cache-timeout'):
         vxlan['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout'))
 
     # Enable proxy-arp on this interface
     if conf.exists('ip enable-proxy-arp'):
         vxlan['ip_proxy_arp'] = 1
 
     # VXLAN underlay interface
     if conf.exists('link'):
         vxlan['link'] = conf.return_value('link')
 
     # Maximum Transmission Unit (MTU)
     if conf.exists('mtu'):
         vxlan['mtu'] = int(conf.return_value('mtu'))
 
     # Remote address of VXLAN tunnel
     if conf.exists('remote'):
         vxlan['remote'] = conf.return_value('remote')
 
     # Remote port of VXLAN tunnel
     if conf.exists('port'):
         vxlan['remote_port'] = int(conf.return_value('port'))
 
     # Virtual Network Identifier
     if conf.exists('vni'):
         vxlan['vni'] = conf.return_value('vni')
 
     return vxlan
 
 
 def verify(vxlan):
     if vxlan['deleted']:
         # bail out early
         return None
 
     if vxlan['mtu'] < 1500:
         print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
 
     if vxlan['group'] and not vxlan['link']:
         raise ConfigError('Multicast VXLAN requires an underlaying interface ')
 
     if not (vxlan['group'] or vxlan['remote']):
         raise ConfigError('Group or remote must be configured')
 
     if not vxlan['vni']:
         raise ConfigError('Must configure VNI for VXLAN')
 
     if vxlan['link']:
         # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU
         # if our configured MTU is at least 50 bytes less
         underlay_mtu = int(Interface(vxlan['link']).get_mtu())
         if underlay_mtu < (vxlan['mtu'] + 50):
             raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \
                               'MTU is to small ({})'.format(underlay_mtu))
 
     return None
 
 
 def generate(vxlan):
     return None
 
 
 def apply(vxlan):
     # Check if the VXLAN interface already exists
     if vxlan['intf'] in interfaces():
         v = VXLANIf(vxlan['intf'])
         # VXLAN is super picky and the tunnel always needs to be recreated,
         # thus we can simply always delete it first.
         v.remove()
 
     if not vxlan['deleted']:
         # VXLAN interface needs to be created on-block
         # instead of passing a ton of arguments, I just use a dict
         # that is managed by vyos.ifconfig
         conf = deepcopy(VXLANIf.get_config())
 
         # Assign VXLAN instance configuration parameters to config dict
         conf['vni'] = vxlan['vni']
         conf['group'] = vxlan['group']
         conf['dev'] = vxlan['link']
         conf['remote'] = vxlan['remote']
         conf['port'] = vxlan['remote_port']
 
         # Finally create the new interface
         v = VXLANIf(vxlan['intf'], config=conf)
         # update interface description used e.g. by SNMP
         v.set_alias(vxlan['description'])
         # Maximum Transfer Unit (MTU)
         v.set_mtu(vxlan['mtu'])
 
         # configure ARP cache timeout in milliseconds
         v.set_arp_cache_tmo(vxlan['ip_arp_cache_tmo'])
         # Enable proxy-arp on this interface
         v.set_proxy_arp(vxlan['ip_proxy_arp'])
 
         # Configure interface address(es) - no need to implicitly delete the
         # old addresses as they have already been removed by deleting the
         # interface above
         for addr in vxlan['address']:
             v.add_addr(addr)
 
         # As the bond interface is always disabled first when changing
         # parameters we will only re-enable the interface if it is not
         # administratively disabled
         if not vxlan['disable']:
             v.set_state('up')
 
     return None
 
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py
index e9a14ae98..7ae62cfb3 100755
--- a/src/helpers/vyos-merge-config.py
+++ b/src/helpers/vyos-merge-config.py
@@ -1,115 +1,112 @@
 #!/usr/bin/python3
 
 # Copyright 2019 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 sys
 import os
 import subprocess
 import tempfile
 import vyos.defaults
 import vyos.remote
 import vyos.migrator
 from vyos.config import Config
 from vyos.configtree import ConfigTree
 
 
 if (len(sys.argv) < 2):
     print("Need config file name to merge.")
     print("Usage: merge <config file> [config path]")
     sys.exit(0)
 
 file_name = sys.argv[1]
 
 configdir = vyos.defaults.directories['config']
 
 protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp']
 
 if any(x in file_name for x in protocols):
     config_file = vyos.remote.get_remote_config(file_name)
     if not config_file:
         sys.exit("No config file by that name.")
 else:
     canonical_path = "{0}/{1}".format(configdir, file_name)
     first_err = None
     try:
         with open(canonical_path, 'r') as f:
             config_file = f.read()
     except Exception as err:
         first_err = err
         try:
             with open(file_name, 'r') as f:
                 config_file = f.read()
         except Exception as err:
             print(first_err)
             print(err)
             sys.exit(1)
 
 with tempfile.NamedTemporaryFile() as file_to_migrate:
     with open(file_to_migrate.name, 'w') as fd:
         fd.write(config_file)
 
     migration = vyos.migrator.Migrator(file_to_migrate.name)
     migration.run()
     if migration.config_changed():
         with open(file_to_migrate.name, 'r') as fd:
             config_file = fd.read()
 
 merge_config_tree = ConfigTree(config_file)
 
 effective_config = Config()
 
 output_effective_config = effective_config.show_config()
-# showConfig (called by config.show_config() does not escape
-# backslashes, which configtree expects; cf. T1001.
-output_effective_config = output_effective_config.replace("\\", "\\\\")
 
 effective_config_tree = ConfigTree(output_effective_config)
 
 effective_cmds = effective_config_tree.to_commands()
 merge_cmds = merge_config_tree.to_commands()
 
 effective_cmd_list = effective_cmds.splitlines()
 merge_cmd_list =  merge_cmds.splitlines()
 
 effective_cmd_set = set(effective_cmd_list)
 add_cmds = [ cmd for cmd in merge_cmd_list if cmd not in effective_cmd_set ]
 
 path = None
 if (len(sys.argv) > 2):
     path = sys.argv[2:]
     if (not effective_config_tree.exists(path) and not
             merge_config_tree.exists(path)):
         print("path {} does not exist in either effective or merge"
               " config; will use root.".format(path))
         path = None
     else:
         path = " ".join(path)
 
 if path:
     add_cmds = [ cmd for cmd in add_cmds if path in cmd ]
 
 for cmd in add_cmds:
     cmd = "/opt/vyatta/sbin/my_" + cmd
 
     try:
         subprocess.check_call(cmd, shell=True)
     except subprocess.CalledProcessError as err:
         print("Called process error: {}.".format(err))
 
 if effective_config.session_changed():
     print("Merge complete. Use 'commit' to make changes effective.")
 else:
     print("No configuration changes to commit.")
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index e3644e063..46ebf5ffb 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -1,162 +1,166 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program 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 General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 import sys
 import argparse
 import subprocess
 import re
 
 from datetime import datetime, timedelta, time as type_time, date as type_date
 from subprocess import check_output, CalledProcessError, STDOUT
 from vyos.util import ask_yes_no
 
 
 def valid_time(s):
   try:
     return datetime.strptime(s, "%H:%M").time()
   except ValueError:
     return None
 
 
 def valid_date(s):
   try:
     return datetime.strptime(s, "%d%m%Y").date()
   except ValueError:
     try:
       return datetime.strptime(s, "%d/%m/%Y").date()
     except ValueError:
       try:
         return datetime.strptime(s, "%d.%m.%Y").date()
       except ValueError:
         try:
           return datetime.strptime(s, "%d:%m:%Y").date()
         except ValueError:
           return None
 
 
 def check_shutdown():
   try:
     cmd = check_output(["/bin/systemctl","status","systemd-shutdownd.service"])
     #Shutodwn is scheduled
     r = re.findall(r'Status: \"(.*)\"\n', cmd.decode())[0]
     print(r)
   except CalledProcessError as e:
     #Shutdown is not scheduled
     print("Shutdown is not scheduled")
 
 def cancel_shutdown():
   try:
-    cmd = check_output(["/sbin/shutdown","-c"])
+    timenow = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+    cmd = check_output(["/sbin/shutdown","-c","--no-wall"])
+    message = "Reboot scheduled has been cancelled %s" % timenow
+    #Generate broadcast message about cancel reboot
+    os.system("wall %s" % message)
   except CalledProcessError as e:
     sys.exit("Error aborting shutdown: %s" % e)
 
 def execute_shutdown(time, reboot = True, ask=True):
   if not ask:
     action = "reboot" if reboot else "poweroff"
     if not ask_yes_no("Are you sure you want to %s this system?" % action):
       sys.exit(0)
 
   action = "-r" if reboot else "-P"
 
   if len(time) == 0:
     ### T870 legacy reboot job support
     chk_vyatta_based_reboots()
     ###
 
     cmd = check_output(["/sbin/shutdown",action,"now"],stderr=STDOUT)
     print(cmd.decode().split(",",1)[0])
     return
 
   # Try to extract date from the first argument
   if len(time) == 1:
     time = time[0].split(" ",1)
 
   if len(time) == 1:
     ts = valid_time(time[0])
     if time[0].isdigit() or valid_time(time[0]):
       cmd = check_output(["/sbin/shutdown",action,time[0]],stderr=STDOUT)
     else:
       sys.exit("Timestamp needs to be in format of 12:34")
 
   elif len(time) == 2:
     ts = valid_time(time[0])
     ds = valid_date(time[1])
     if ts and ds:
       t = datetime.combine(ds, ts)
       td = t - datetime.now()
       t2 = 1 + int(td.total_seconds())//60 # Get total minutes
       cmd = check_output(["/sbin/shutdown",action,str(t2)],stderr=STDOUT)
     else:
       sys.exit("Timestamp needs to be in format of 12:34\nDatestamp in the format of DD.MM.YY")
   else:
     sys.exit("Could not decode time and date")
 
-  print(cmd.decode().split(",",1)[0])
+  check_shutdown()
 
 def chk_vyatta_based_reboots():
   ### T870 commit-confirm is still using the vyatta code base, once gone, the code below can be removed
   ### legacy scheduled reboot s are using at and store the is as /var/run/<name>.job
   ### name is the node of scheduled the job, commit-confirm checks for that
 
   f = r'/var/run/confirm.job'
   if os .path.exists(f):
     jid = open(f).read().strip()
     if jid != 0:
       subprocess.call(['sudo', 'atrm', jid])
     os.remove(f)
 
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument("--yes", "-y",
             help="dont as for shutdown",
             action="store_true",
             dest="yes")
   action = parser.add_mutually_exclusive_group(required=True)
   action.add_argument("--reboot", "-r",
             help="Reboot the system",
             nargs="*",
             metavar="Minutes|HH:MM")
 
   action.add_argument("--poweroff", "-p",
             help="Poweroff the system",
             nargs="*",
             metavar="Minutes|HH:MM")
 
   action.add_argument("--cancel", "-c",
             help="Cancel pending shutdown",
             action="store_true")
 
   action.add_argument("--check",
             help="Check pending chutdown",
             action="store_true")
   args = parser.parse_args()
 
   try:
     if  args.reboot is not None:
       execute_shutdown(args.reboot, reboot=True, ask=args.yes)
     if args.poweroff is not None:
       execute_shutdown(args.poweroff, reboot=False,ask=args.yes)
     if args.cancel:
       cancel_shutdown()
     if args.check:
       check_shutdown()
   except KeyboardInterrupt:
     sys.exit("Interrupted")
 
 
 if __name__ == "__main__":
   main()
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 9b6d7e979..1abaed873 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -1,337 +1,335 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2019 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program 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 General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 #
 
 import os
 import sys
 import grp
 import json
 import traceback
 import threading
 
 import vyos.config
-import vyos.util
 
 import bottle
 
 from functools import wraps
 
 from vyos.configsession import ConfigSession, ConfigSessionError
 from vyos.config import VyOSError
 
 
 DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf'
 CFG_GROUP = 'vyattacfg'
 
 app = bottle.default_app()
 
 # Giant lock!
 lock = threading.Lock()
 
 def load_server_config():
     with open(DEFAULT_CONFIG_FILE) as f:
         config = json.load(f)
     return config
 
 def check_auth(key_list, key):
     id = None
     for k in key_list:
         if k['key'] == key:
             id = k['id']
     return id
 
 def error(code, msg):
     bottle.response.status = code
     resp = {"success": False, "error": msg, "data": None}
     return json.dumps(resp)
 
 def success(data):
     resp = {"success": True, "data": data, "error": None}
     return json.dumps(resp)
 
 def auth_required(f):
     @wraps(f)
     def decorated_function(*args, **kwargs):
         key = bottle.request.forms.get("key")
         api_keys = app.config['vyos_keys']
         id = check_auth(api_keys, key)
         if not id:
             return error(401, "Valid API key is required")
         return f(*args, **kwargs)
 
     return decorated_function
 
 @app.route('/configure', method='POST')
 @auth_required
 def configure():
     session = app.config['vyos_session']
     config = app.config['vyos_config']
 
     strict_field = bottle.request.forms.get("strict")
     if strict_field == "true":
         strict = True
     else:
         strict = False
 
     commands = bottle.request.forms.get("data")
     if not commands:
         return error(400, "Non-empty data field is required")
     else:
         try:
             commands = json.loads(commands)
         except Exception as e:
             return error(400, "Failed to parse JSON: {0}".format(e))
 
     # Allow users to pass just one command
     if not isinstance(commands, list):
         commands = [commands]
 
     # We don't want multiple people/apps to be able to commit at once,
     # or modify the shared session while someone else is doing the same,
     # so the lock is really global
     lock.acquire()
 
     status = 200
     error_msg = None
     try:
         for c in commands:
             # What we've got may not even be a dict
             if not isinstance(c, dict):
                 raise ConfigSessionError("Malformed command \"{0}\": any command must be a dict".format(json.dumps(c)))
 
             # Missing op or path is a show stopper
             if not ('op' in c):
                 raise ConfigSessionError("Malformed command \"{0}\": missing \"op\" field".format(json.dumps(c)))
             if not ('path' in c):
                 raise ConfigSessionError("Malformed command \"{0}\": missing \"path\" field".format(json.dumps(c)))
 
             # Missing value is fine, substitute for empty string
             if 'value' in c:
                 value = c['value']
             else:
                 value = ""
 
             op = c['op']
             path = c['path']
 
             if not path:
                 raise ConfigSessionError("Malformed command \"{0}\": empty path".format(json.dumps(c)))
 
             # Type checking
             if not isinstance(path, list):
                 raise ConfigSessionError("Malformed command \"{0}\": \"path\" field must be a list".format(json.dumps(c)))
 
             if not isinstance(value, str):
                 raise ConfigSessionError("Malformed command \"{0}\": \"value\" field must be a string".format(json.dumps(c)))
 
             # Account for the case when value field is present and set to null
             if not value:
                 value = ""
 
             # For vyos.configsessios calls that have no separate value arguments,
             # and for type checking too
             try:
                 cfg_path = " ".join(path + [value]).strip()
             except TypeError:
                 raise ConfigSessionError("Malformed command \"{0}\": \"path\" field must be a list of strings".format(json.dumps(c)))
 
             if op == 'set':
                 # XXX: it would be nice to do a strict check for "path already exists",
                 # but there's probably no way to do that
                 session.set(path, value=value)
             elif op == 'delete':
                 if strict and not config.exists(cfg_path):
                     raise ConfigSessionError("Cannot delete [{0}]: path/value does not exist".format(cfg_path))
                 session.delete(path, value=value)
             elif op == 'comment':
                 session.comment(path, value=value)
             else:
                 raise ConfigSessionError("\"{0}\" is not a valid operation".format(op))
         # end for
         session.commit()
         print("Configuration modified via HTTP API using key \"{0}\"".format(id))
     except ConfigSessionError as e:
         session.discard()
         status = 400
         if app.config['vyos_debug']:
             print(traceback.format_exc(), file=sys.stderr)
         error_msg = str(e)
     except Exception as e:
         session.discard()
         print(traceback.format_exc(), file=sys.stderr)
         status = 500
 
         # Don't give the details away to the outer world
         error_msg = "An internal error occured. Check the logs for details."
     finally:
         lock.release()
 
     if status != 200:
         return error(status, error_msg)
     else:
         return success(None)
 
 @app.route('/retrieve', method='POST')
 @auth_required
 def get_value():
     config = app.config['vyos_config']
     session = app.config['vyos_session']
 
     command = bottle.request.forms.get("data")
     command = json.loads(command)
 
     try:
         op = command['op']
         path = " ".join(command['path'])
     except KeyError:
         return error(400, "Missing required field. \"op\" and \"path\" fields are required")
 
     try:
         if op == 'returnValue':
             res = config.return_value(path)
         elif op == 'returnValues':
             res = config.return_values(path)
         elif op == 'exists':
             res = config.exists(path)
         elif op == 'showConfig':
             config_format = 'json'
             if 'configFormat' in command:
                 config_format = command['configFormat']
 
             res = session.show_config(path=command['path'])
-            res = vyos.util.escape_backslash(res)
             if config_format == 'json':
                 config_tree = vyos.configtree.ConfigTree(res)
                 res = json.loads(config_tree.to_json())
             elif config_format == 'json_ast':
                 config_tree = vyos.configtree.ConfigTree(res)
                 res = json.loads(config_tree.to_json_ast())
             elif config_format == 'raw':
                 pass
             else:
                 return error(400, "\"{0}\" is not a valid config format".format(config_format))
         else:
             return error(400, "\"{0}\" is not a valid operation".format(op))
     except VyOSError as e:
         return error(400, str(e))
     except Exception as e:
         print(traceback.format_exc(), file=sys.stderr)
         return error(500, "An internal error occured. Check the logs for details.")
 
     return success(res)
 
 @app.route('/config-file', method='POST')
 @auth_required
 def config_file_op():
     config = app.config['vyos_config']
     session = app.config['vyos_session']
 
     command = bottle.request.forms.get("data")
     command = json.loads(command)
 
     try:
         op = command['op']
     except KeyError:
         return error(400, "Missing required field \"op\"")
 
     try:
         if op == 'save':
             try:
                 path = command['file']
             except KeyError:
                 path = '/config/config.boot'
             res = session.save_config(path)
         elif op == 'load':
             try:
                 path = command['file']
             except KeyError:
                 return error(400, "Missing required field \"file\"")
             res = session.load_config(path)
             res = session.commit()
         else:
             return error(400, "\"{0}\" is not a valid operation".format(op))
     except VyOSError as e:
         return error(400, str(e))
     except Exception as e:
         print(traceback.format_exc(), file=sys.stderr)
         return error(500, "An internal error occured. Check the logs for details.")
 
     return success(res)
 
 @app.route('/image', method='POST')
 @auth_required
 def config_file_op():
     config = app.config['vyos_config']
     session = app.config['vyos_session']
 
     command = bottle.request.forms.get("data")
     command = json.loads(command)
 
     try:
         op = command['op']
     except KeyError:
         return error(400, "Missing required field \"op\"")
 
     try:
         if op == 'add':
             try:
                 url = command['url']
             except KeyError:
                 return error(400, "Missing required field \"url\"")
             res = session.install_image(url)
         elif op == 'delete':
             try:
                 name = command['name']
             except KeyError:
                 return error(400, "Missing required field \"name\"")
             res = session.remove_image(name)
         else:
             return error(400, "\"{0}\" is not a valid operation".format(op))
     except VyOSError as e:
         return error(400, str(e))
     except Exception as e:
         print(traceback.format_exc(), file=sys.stderr)
         return error(500, "An internal error occured. Check the logs for details.")
 
     return success(res)
 
 
 if __name__ == '__main__':
     # systemd's user and group options don't work, do it by hand here,
     # else no one else will be able to commit
     cfg_group = grp.getgrnam(CFG_GROUP)
     os.setgid(cfg_group.gr_gid)
 
     # Need to set file permissions to 775 too so that every vyattacfg group member
     # has write access to the running config
     os.umask(0o002)
 
     try:
         server_config = load_server_config()
     except Exception as e:
         print("Failed to load the HTTP API server config: {0}".format(e))
 
     session = ConfigSession(os.getpid())
     env = session.get_session_env()
     config = vyos.config.Config(session_env=env)
 
     app.config['vyos_session'] = session
     app.config['vyos_config'] = config
     app.config['vyos_keys'] = server_config['api_keys']
     app.config['vyos_debug'] = server_config['debug']
 
     bottle.run(app, host=server_config["listen_address"], port=server_config["port"], debug=True)
diff --git a/src/utils/vyos-config-to-commands b/src/utils/vyos-config-to-commands
index 7147bc5ff..8b50f7c5d 100755
--- a/src/utils/vyos-config-to-commands
+++ b/src/utils/vyos-config-to-commands
@@ -1,37 +1,29 @@
 #!/usr/bin/python3
 
 import sys
 
 from signal import signal, SIGPIPE, SIG_DFL
 from vyos.configtree import ConfigTree
 
 signal(SIGPIPE,SIG_DFL)
 
 config_string = None
 if (len(sys.argv) == 1):
     # If no argument given, act as a pipe
     config_string = sys.stdin.read()
 else:
     file_name = sys.argv[1]
     try:
         with open(file_name, 'r') as f:
             config_string = f.read()
     except OSError as e:
         print("Could not read config file {0}: {1}".format(file_name, e), file=sys.stderr)
 
-# This script is usually called with the output of "cli-shell-api showCfg", which does not
-# escape backslashes. "ConfigTree()" expects escaped backslashes when parsing a config
-# string (and also prints them itself). Therefore this script would fail.
-# Manually escape backslashes here to handle backslashes in any configuration strings
-# properly. The alternative would be to modify the output of "cli-shell-api showCfg",
-# but that may be break other things who rely on that specific output.
-config_string = config_string.replace("\\", "\\\\")
-
 try:
     config = ConfigTree(config_string)
     commands = config.to_commands()
 except ValueError as e:
     print("Could not parse the config file: {0}".format(e), file=sys.stderr)
     sys.exit(1)
 
 print(commands)