"""SELinux utility functions"""

import errno
import os
import subprocess
from typing import Dict, List, Optional, TextIO

# Extended attribute name for SELinux labels
XATTR_NAME_SELINUX = b"security.selinux"


def parse_config(config_file: TextIO):
    """Parse an SELinux configuration file"""
    config = {}
    for line in config_file:
        line = line.strip()
        if not line:
            continue
        if line.startswith('#'):
            continue
        k, v = line.split('=', 1)
        config[k.strip()] = v.strip()
    return config


def config_get_policy(config: Dict[str, str]):
    """Return the effective SELinux policy

    Checks if SELinux is enabled and if so returns the
    policy; otherwise `None` is returned.
    """
    enabled = config.get('SELINUX', 'disabled')
    if enabled not in ['enforcing', 'permissive']:
        return None
    return config.get('SELINUXTYPE', None)


def setfiles(spec_file: str, root: str, *paths, exclude_paths: Optional[List[str]] = None) -> None:
    """Initialize the security context fields for `paths`

    Initialize the security context fields (extended attributes)
    on `paths` using the given specification in `spec_file`. The
    `root` argument determines the root path of the file system
    and the entries in `path` are interpreted as relative to it.
    Uses the setfiles(8) tool to actually set the contexts.
    Paths can be excluded via the exclude_paths argument.
    """
    if exclude_paths is None:
        exclude_paths = []
    exclude_paths_args = []
    for p in exclude_paths:
        exclude_paths_args.extend(["-e", p])

    for path in paths:
        subprocess.run(["setfiles", "-F",
                        "-r", root,
                        *exclude_paths_args,
                        spec_file,
                        f"{root}{path}"],
                       check=True)


def getfilecon(path: str) -> str:
    """Get the security context associated with `path`"""
    label = os.getxattr(path, XATTR_NAME_SELINUX,
                        follow_symlinks=False)
    return label.decode().strip('\n\0')


def setfilecon(path: str, context: str) -> None:
    """
    Set the security context associated with `path`

    Like `setfilecon`(3), but does not attempt to translate
    the context via `selinux_trans_to_raw_context`.
    """

    try:
        os.setxattr(path, XATTR_NAME_SELINUX,
                    context.encode(),
                    follow_symlinks=True)
    except OSError as err:
        # in case we get a not-supported error, check if
        # the context we want to set is already set and
        # ignore the error in that case. This follows the
        # behavior of `setfilecon(3)`.
        if err.errno == errno.ENOTSUP:
            have = getfilecon(path)
            if have == context:
                return
        raise
