# Copyright 2014, 2016, Tresys Technology, LLC
#
# This file is part of SETools.
#
# SETools 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.
#
# SETools 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 SETools.  If not, see
# <http://www.gnu.org/licenses/>.
#
import stat

from . import exception
from . import qpol
from . import symbol
from . import context


def validate_ruletype(t):
    """Validate fs_use_* rule types."""
    if t not in ["fs_use_xattr", "fs_use_trans", "fs_use_task"]:
        raise exception.InvalidFSUseType("{0} is not a valid fs_use_* type.".format(t))

    return t


def fs_use_factory(policy, name):
    """Factory function for creating fs_use_* objects."""

    if not isinstance(name, qpol.qpol_fs_use_t):
        raise TypeError("fs_use_* cannot be looked-up.")

    return FSUse(policy, name)


def genfscon_factory(policy, name):
    """Factory function for creating genfscon objects."""

    if not isinstance(name, qpol.qpol_genfscon_t):
        raise TypeError("Genfscons cannot be looked-up.")

    return Genfscon(policy, name)


class FSContext(symbol.PolicySymbol):

    """Base class for in-policy labeling rules."""

    def __str__(self):
        raise NotImplementedError

    @property
    def fs(self):
        """The filesystem type for this statement."""
        return self.qpol_symbol.name(self.policy)

    @property
    def context(self):
        """The context for this statement."""
        return context.context_factory(self.policy, self.qpol_symbol.context(self.policy))

    def statement(self):
        return str(self)


class GenfsFiletype(int):

    """
    A genfscon file type.

    The possible values are equivalent to file type
    values in the stat module, e.g. S_IFBLK, but
    overrides the string representation with the
    corresponding genfscon file type string
    (-b, -c, etc.)  If the genfscon has no specific
    file type, this is 0, (empty string).
    """

    _filetype_to_text = {
        0: "",
        stat.S_IFBLK: "-b",
        stat.S_IFCHR: "-c",
        stat.S_IFDIR: "-d",
        stat.S_IFIFO: "-p",
        stat.S_IFREG: "--",
        stat.S_IFLNK: "-l",
        stat.S_IFSOCK: "-s"}

    def __str__(self):
        return self._filetype_to_text[self]


class Genfscon(FSContext):

    """A genfscon statement."""

    def __str__(self):
        return "genfscon {0.fs} {0.path} {0.filetype} {0.context}".format(self)

    def __hash__(self):
        return hash("genfscon|{0.fs}|{0.path}|{0.filetype}".format(self))

    def __eq__(self, other):
        # Libqpol allocates new C objects in the
        # genfscons iterator, so pointer comparison
        # in the PolicySymbol object doesn't work.
        try:
            return (self.fs == other.fs and
                    self.path == other.path and
                    self.filetype == other.filetype and
                    self.context == other.context)
        except AttributeError:
            return str(self) == str(other)

    @property
    def filetype(self):
        """The file type (e.g. stat.S_IFBLK) for this genfscon statement."""
        return GenfsFiletype(self.qpol_symbol.object_class(self.policy))

    @property
    def path(self):
        """The path for this genfscon statement."""
        return self.qpol_symbol.path(self.policy)


class FSUse(FSContext):

    """A fs_use_* statement."""

    # there are more rule types, but modern SELinux
    # only supports these three.
    _ruletype_to_text = {
        qpol.QPOL_FS_USE_XATTR: 'fs_use_xattr',
        qpol.QPOL_FS_USE_TRANS: 'fs_use_trans',
        qpol.QPOL_FS_USE_TASK: 'fs_use_task'}

    def __str__(self):
        return "{0.ruletype} {0.fs} {0.context};".format(self)

    def __hash__(self):
        return hash("{0.ruletype}|{0.fs}".format(self))

    @property
    def ruletype(self):
        """The rule type for this fs_use_* statement."""
        return self._ruletype_to_text[self.qpol_symbol.behavior(self.policy)]