//! Feature flags for *pxar* allow to control what is stored/restored in/from the
//! archive.
//! Flags for known supported features for a given filesystem can be derived
//! from the superblocks magic number.

use libc::c_long;

use bitflags::bitflags;

bitflags! {
    pub struct Flags: u64 {
        /// FAT-style 2s time granularity
        const WITH_2SEC_TIME                   = 0x40;
        /// Preserve read only flag of files
        const WITH_READ_ONLY                   = 0x80;
        /// Preserve unix permissions
        const WITH_PERMISSIONS                 = 0x100;
        /// Include symbolik links
        const WITH_SYMLINKS                    = 0x200;
        /// Include device nodes
        const WITH_DEVICE_NODES                = 0x400;
        /// Include FIFOs
        const WITH_FIFOS                       = 0x800;
        /// Include Sockets
        const WITH_SOCKETS                     = 0x1000;

        /// Preserve DOS file flag `HIDDEN`
        const WITH_FLAG_HIDDEN                 = 0x2000;
        /// Preserve DOS file flag `SYSTEM`
        const WITH_FLAG_SYSTEM                 = 0x4000;
        /// Preserve DOS file flag `ARCHIVE`
        const WITH_FLAG_ARCHIVE                = 0x8000;

        // chattr() flags
        /// Linux file attribute `APPEND`
        const WITH_FLAG_APPEND                 = 0x10000;
        /// Linux file attribute `NOATIME`
        const WITH_FLAG_NOATIME                = 0x20000;
        /// Linux file attribute `COMPR`
        const WITH_FLAG_COMPR                  = 0x40000;
        /// Linux file attribute `NOCOW`
        const WITH_FLAG_NOCOW                  = 0x80000;
        /// Linux file attribute `NODUMP`
        const WITH_FLAG_NODUMP                 = 0x0010_0000;
        /// Linux file attribute `DIRSYNC`
        const WITH_FLAG_DIRSYNC                = 0x0020_0000;
        /// Linux file attribute `IMMUTABLE`
        const WITH_FLAG_IMMUTABLE              = 0x0040_0000;
        /// Linux file attribute `SYNC`
        const WITH_FLAG_SYNC                   = 0x0080_0000;
        /// Linux file attribute `NOCOMP`
        const WITH_FLAG_NOCOMP                 = 0x0100_0000;
        /// Linux file attribute `PROJINHERIT`
        const WITH_FLAG_PROJINHERIT            = 0x0200_0000;


        /// Preserve BTRFS subvolume flag
        const WITH_SUBVOLUME                   = 0x0400_0000;
        /// Preserve BTRFS read-only subvolume flag
        const WITH_SUBVOLUME_RO                = 0x0800_0000;

        /// Preserve Extended Attribute metadata
        const WITH_XATTRS                      = 0x1000_0000;
        /// Preserve Access Control List metadata
        const WITH_ACL                         = 0x2000_0000;
        /// Preserve SELinux security context
        const WITH_SELINUX                     = 0x4000_0000;
        /// Preserve "security.capability" xattr
        const WITH_FCAPS                       = 0x8000_0000;

        /// Preserve XFS/ext4/ZFS project quota ID
        const WITH_QUOTA_PROJID                = 0x0001_0000_0000;

        /// Support ".pxarexclude" files
        const EXCLUDE_FILE                     = 0x1000_0000_0000_0000;
        /// Exclude submounts
        const EXCLUDE_SUBMOUNTS                = 0x4000_0000_0000_0000;
        /// Exclude entries with chattr flag NODUMP
        const EXCLUDE_NODUMP                   = 0x8000_0000_0000_0000;

        // Definitions of typical feature flags for the *pxar* encoder/decoder.
        // By this expensive syscalls for unsupported features are avoided.

        /// All chattr file attributes
        const WITH_CHATTR =
            Flags::WITH_FLAG_APPEND.bits() |
            Flags::WITH_FLAG_NOATIME.bits() |
            Flags::WITH_FLAG_COMPR.bits() |
            Flags::WITH_FLAG_NOCOW.bits() |
            Flags::WITH_FLAG_NODUMP.bits() |
            Flags::WITH_FLAG_DIRSYNC.bits() |
            Flags::WITH_FLAG_IMMUTABLE.bits() |
            Flags::WITH_FLAG_SYNC.bits() |
            Flags::WITH_FLAG_NOCOMP.bits() |
            Flags::WITH_FLAG_PROJINHERIT.bits();

        /// All FAT file attributes
        const WITH_FAT_ATTRS =
            Flags::WITH_FLAG_HIDDEN.bits() |
            Flags::WITH_FLAG_SYSTEM.bits() |
            Flags::WITH_FLAG_ARCHIVE.bits();

        /// All bits that may also be exposed via fuse
        const WITH_FUSE =
            Flags::WITH_2SEC_TIME.bits() |
            Flags::WITH_READ_ONLY.bits() |
            Flags::WITH_PERMISSIONS.bits() |
            Flags::WITH_SYMLINKS.bits() |
            Flags::WITH_DEVICE_NODES.bits() |
            Flags::WITH_FIFOS.bits() |
            Flags::WITH_SOCKETS.bits() |
            Flags::WITH_FAT_ATTRS.bits() |
            Flags::WITH_CHATTR.bits() |
            Flags::WITH_XATTRS.bits();


        /// Default feature flags for encoder/decoder
        const DEFAULT =
            Flags::WITH_SYMLINKS.bits() |
            Flags::WITH_DEVICE_NODES.bits() |
            Flags::WITH_FIFOS.bits() |
            Flags::WITH_SOCKETS.bits() |
            Flags::WITH_FLAG_HIDDEN.bits() |
            Flags::WITH_FLAG_SYSTEM.bits() |
            Flags::WITH_FLAG_ARCHIVE.bits() |
            Flags::WITH_FLAG_APPEND.bits() |
            Flags::WITH_FLAG_NOATIME.bits() |
            Flags::WITH_FLAG_COMPR.bits() |
            Flags::WITH_FLAG_NOCOW.bits() |
            //WITH_FLAG_NODUMP.bits() |
            Flags::WITH_FLAG_DIRSYNC.bits() |
            Flags::WITH_FLAG_IMMUTABLE.bits() |
            Flags::WITH_FLAG_SYNC.bits() |
            Flags::WITH_FLAG_NOCOMP.bits() |
            Flags::WITH_FLAG_PROJINHERIT.bits() |
            Flags::WITH_SUBVOLUME.bits() |
            Flags::WITH_SUBVOLUME_RO.bits() |
            Flags::WITH_XATTRS.bits() |
            Flags::WITH_ACL.bits() |
            Flags::WITH_SELINUX.bits() |
            Flags::WITH_FCAPS.bits() |
            Flags::WITH_QUOTA_PROJID.bits() |
            Flags::EXCLUDE_NODUMP.bits() |
            Flags::EXCLUDE_FILE.bits();
    }
}

impl Default for Flags {
    fn default() -> Flags {
        Flags::DEFAULT
    }
}

// form /usr/include/linux/fs.h
const FS_APPEND_FL: c_long =      0x0000_0020;
const FS_NOATIME_FL: c_long =     0x0000_0080;
const FS_COMPR_FL: c_long =       0x0000_0004;
const FS_NOCOW_FL: c_long =       0x0080_0000;
const FS_NODUMP_FL: c_long =      0x0000_0040;
const FS_DIRSYNC_FL: c_long =     0x0001_0000;
const FS_IMMUTABLE_FL: c_long =   0x0000_0010;
const FS_SYNC_FL: c_long =        0x0000_0008;
const FS_NOCOMP_FL: c_long =      0x0000_0400;
const FS_PROJINHERIT_FL: c_long = 0x2000_0000;

pub(crate) const INITIAL_FS_FLAGS: c_long =
    FS_NOATIME_FL
    | FS_COMPR_FL
    | FS_NOCOW_FL
    | FS_NOCOMP_FL
    | FS_PROJINHERIT_FL;

#[rustfmt::skip]
const CHATTR_MAP: [(Flags, c_long); 10] = [
    ( Flags::WITH_FLAG_APPEND,      FS_APPEND_FL      ),
    ( Flags::WITH_FLAG_NOATIME,     FS_NOATIME_FL     ),
    ( Flags::WITH_FLAG_COMPR,       FS_COMPR_FL       ),
    ( Flags::WITH_FLAG_NOCOW,       FS_NOCOW_FL       ),
    ( Flags::WITH_FLAG_NODUMP,      FS_NODUMP_FL      ),
    ( Flags::WITH_FLAG_DIRSYNC,     FS_DIRSYNC_FL     ),
    ( Flags::WITH_FLAG_IMMUTABLE,   FS_IMMUTABLE_FL   ),
    ( Flags::WITH_FLAG_SYNC,        FS_SYNC_FL        ),
    ( Flags::WITH_FLAG_NOCOMP,      FS_NOCOMP_FL      ),
    ( Flags::WITH_FLAG_PROJINHERIT, FS_PROJINHERIT_FL ),
];

// from /usr/include/linux/msdos_fs.h
const ATTR_HIDDEN: u32 =      2;
const ATTR_SYS: u32 =         4;
const ATTR_ARCH: u32 =       32;

#[rustfmt::skip]
const FAT_ATTR_MAP: [(Flags, u32); 3] = [
    ( Flags::WITH_FLAG_HIDDEN,  ATTR_HIDDEN ),
    ( Flags::WITH_FLAG_SYSTEM,  ATTR_SYS    ),
    ( Flags::WITH_FLAG_ARCHIVE, ATTR_ARCH   ),
];

impl Flags {
    /// Get a set of feature flags from file attributes.
    pub fn from_chattr(attr: c_long) -> Flags {
        let mut flags = Flags::empty();

        for (fe_flag, fs_flag) in &CHATTR_MAP {
            if (attr & fs_flag) != 0 {
                flags |= *fe_flag;
            }
        }

        flags
    }

    /// Get the chattr bit representation of these feature flags.
    pub fn to_chattr(self) -> c_long {
        let mut flags: c_long = 0;

        for (fe_flag, fs_flag) in &CHATTR_MAP {
            if self.contains(*fe_flag) {
                flags |= *fs_flag;
            }
        }

        flags
    }

    pub fn to_initial_chattr(self) -> c_long {
        self.to_chattr() & INITIAL_FS_FLAGS
    }

    /// Get a set of feature flags from FAT attributes.
    pub fn from_fat_attr(attr: u32) -> Flags {
        let mut flags = Flags::empty();

        for (fe_flag, fs_flag) in &FAT_ATTR_MAP {
            if (attr & fs_flag) != 0 {
                flags |= *fe_flag;
            }
        }

        flags
    }

    /// Get the fat attribute bit representation of these feature flags.
    pub fn to_fat_attr(self) -> u32 {
        let mut flags = 0u32;

        for (fe_flag, fs_flag) in &FAT_ATTR_MAP {
            if self.contains(*fe_flag) {
                flags |= *fs_flag;
            }
        }

        flags
    }

    /// Return the supported *pxar* feature flags based on the magic number of the filesystem.
    pub fn from_magic(magic: i64) -> Flags {
        use proxmox::sys::linux::magic::*;
        match magic {
            MSDOS_SUPER_MAGIC => {
                Flags::WITH_2SEC_TIME |
                Flags::WITH_READ_ONLY |
                Flags::WITH_FAT_ATTRS
            },
            EXT4_SUPER_MAGIC => {
                Flags::WITH_2SEC_TIME |
                Flags::WITH_READ_ONLY |
                Flags::WITH_PERMISSIONS |
                Flags::WITH_SYMLINKS |
                Flags::WITH_DEVICE_NODES |
                Flags::WITH_FIFOS |
                Flags::WITH_SOCKETS |
                Flags::WITH_FLAG_APPEND |
                Flags::WITH_FLAG_NOATIME |
                Flags::WITH_FLAG_NODUMP |
                Flags::WITH_FLAG_DIRSYNC |
                Flags::WITH_FLAG_IMMUTABLE |
                Flags::WITH_FLAG_SYNC |
                Flags::WITH_XATTRS |
                Flags::WITH_ACL |
                Flags::WITH_SELINUX |
                Flags::WITH_FCAPS |
                Flags::WITH_QUOTA_PROJID
            },
            XFS_SUPER_MAGIC => {
                Flags::WITH_2SEC_TIME |
                Flags::WITH_READ_ONLY |
                Flags::WITH_PERMISSIONS |
                Flags::WITH_SYMLINKS |
                Flags::WITH_DEVICE_NODES |
                Flags::WITH_FIFOS |
                Flags::WITH_SOCKETS |
                Flags::WITH_FLAG_APPEND |
                Flags::WITH_FLAG_NOATIME |
                Flags::WITH_FLAG_NODUMP |
                Flags::WITH_FLAG_IMMUTABLE |
                Flags::WITH_FLAG_SYNC |
                Flags::WITH_XATTRS |
                Flags::WITH_ACL |
                Flags::WITH_SELINUX |
                Flags::WITH_FCAPS |
                Flags::WITH_QUOTA_PROJID
            },
            ZFS_SUPER_MAGIC => {
                Flags::WITH_2SEC_TIME |
                Flags::WITH_READ_ONLY |
                Flags::WITH_PERMISSIONS |
                Flags::WITH_SYMLINKS |
                Flags::WITH_DEVICE_NODES |
                Flags::WITH_FIFOS |
                Flags::WITH_SOCKETS |
                Flags::WITH_FLAG_APPEND |
                Flags::WITH_FLAG_NOATIME |
                Flags::WITH_FLAG_NODUMP |
                Flags::WITH_FLAG_DIRSYNC |
                Flags::WITH_FLAG_IMMUTABLE |
                Flags::WITH_FLAG_SYNC |
                Flags::WITH_XATTRS |
                Flags::WITH_ACL |
                Flags::WITH_SELINUX |
                Flags::WITH_FCAPS |
                Flags::WITH_QUOTA_PROJID
            },
            BTRFS_SUPER_MAGIC => {
                Flags::WITH_2SEC_TIME |
                Flags::WITH_READ_ONLY |
                Flags::WITH_PERMISSIONS |
                Flags::WITH_SYMLINKS |
                Flags::WITH_DEVICE_NODES |
                Flags::WITH_FIFOS |
                Flags::WITH_SOCKETS |
                Flags::WITH_FLAG_APPEND |
                Flags::WITH_FLAG_NOATIME |
                Flags::WITH_FLAG_COMPR |
                Flags::WITH_FLAG_NOCOW |
                Flags::WITH_FLAG_NODUMP |
                Flags::WITH_FLAG_DIRSYNC |
                Flags::WITH_FLAG_IMMUTABLE |
                Flags::WITH_FLAG_SYNC |
                Flags::WITH_FLAG_NOCOMP |
                Flags::WITH_XATTRS |
                Flags::WITH_ACL |
                Flags::WITH_SELINUX |
                Flags::WITH_SUBVOLUME |
                Flags::WITH_SUBVOLUME_RO |
                Flags::WITH_FCAPS
            },
            TMPFS_MAGIC => {
                Flags::WITH_2SEC_TIME |
                Flags::WITH_READ_ONLY |
                Flags::WITH_PERMISSIONS |
                Flags::WITH_SYMLINKS |
                Flags::WITH_DEVICE_NODES |
                Flags::WITH_FIFOS |
                Flags::WITH_SOCKETS |
                Flags::WITH_ACL |
                Flags::WITH_SELINUX
            },
            // FUSE mounts are special as the supported feature set
            // is not clear a priori.
            FUSE_SUPER_MAGIC => {
                Flags::WITH_FUSE
            },
            _ => {
                Flags::WITH_2SEC_TIME |
                Flags::WITH_READ_ONLY |
                Flags::WITH_PERMISSIONS |
                Flags::WITH_SYMLINKS |
                Flags::WITH_DEVICE_NODES |
                Flags::WITH_FIFOS |
                Flags::WITH_SOCKETS
            },
        }
    }
}
