packages feed

craft-0.0.0.1: src/Craft/File/Mode.hs

module Craft.File.Mode
( Mode(..)
, ModeSet(..)
, toFileMode
, toMode
, toHuman
, fileModeFromString
, toOctalString
, fromOctalString
)
where

import           Data.Bits       ((.&.), (.|.))
import           Data.Char       (digitToInt)
import           Data.DeriveTH
import           Data.List
import           System.Posix    (FileMode)
import qualified System.Posix
import           Test.QuickCheck (Arbitrary, arbitrary, choose)


toHuman :: Mode -> String
toHuman (Mode u g o) =
  modeSetToHuman u ++ modeSetToHuman g ++ s2t (modeSetToHuman o)
 where
  s2t = map tr
  tr 's' = 't'
  tr 'S' = 'T'
  tr   c = c


data ModeSet
 = O -- ^ O was chosen because it looks like a zero.
 | R
 | W
 | X
 | S
 | RW
 | RX
 | RS
 | WX
 | WS
 | XS
 | RWX
 | RWS
 | RXS
 | WXS
 | RWXS
 deriving (Eq, Enum, Show)

data Mode
  = Mode ModeSet ModeSet ModeSet
  deriving (Eq, Show)


toOctalString :: Mode -> String
toOctalString (Mode u g o) =
  concatMap show $ stickies : map modeSetToOctal l
 where
  stickies = sum $ zipWith (\n m -> n * modeSetToOctalSticky m) [4,2,1] l
  l = [u,g,o]


modeSetToOctal :: ModeSet -> Int
modeSetToOctal O    = 0
modeSetToOctal R    = 4
modeSetToOctal W    = 2
modeSetToOctal X    = 1
modeSetToOctal S    = 0
modeSetToOctal RW   = 6
modeSetToOctal RX   = 5
modeSetToOctal RS   = 4
modeSetToOctal WX   = 3
modeSetToOctal WS   = 2
modeSetToOctal XS   = 1
modeSetToOctal RWX  = 7
modeSetToOctal RWS  = 6
modeSetToOctal RXS  = 5
modeSetToOctal WXS  = 3
modeSetToOctal RWXS = 7


modeSetToOctalSticky :: ModeSet -> Int
modeSetToOctalSticky O    = 0
modeSetToOctalSticky R    = 0
modeSetToOctalSticky W    = 0
modeSetToOctalSticky X    = 0
modeSetToOctalSticky S    = 1
modeSetToOctalSticky RW   = 0
modeSetToOctalSticky RX   = 0
modeSetToOctalSticky RS   = 1
modeSetToOctalSticky WX   = 0
modeSetToOctalSticky WS   = 1
modeSetToOctalSticky XS   = 1
modeSetToOctalSticky RWX  = 0
modeSetToOctalSticky RWS  = 1
modeSetToOctalSticky RXS  = 1
modeSetToOctalSticky WXS  = 1
modeSetToOctalSticky RWXS = 1


modeSetToHuman :: ModeSet -> String
modeSetToHuman O    = "---"
modeSetToHuman R    = "r--"
modeSetToHuman W    = "-w-"
modeSetToHuman X    = "--x"
modeSetToHuman S    = "--S"
modeSetToHuman RW   = "rw-"
modeSetToHuman RX   = "r-x"
modeSetToHuman RS   = "r-S"
modeSetToHuman WX   = "-wx"
modeSetToHuman WS   = "-wS"
modeSetToHuman XS   = "--s"
modeSetToHuman RWX  = "rwx"
modeSetToHuman RWS  = "rwS"
modeSetToHuman RXS  = "r-s"
modeSetToHuman WXS  = "-ws"
modeSetToHuman RWXS = "rws"


fromOctalString :: String -> Mode
fromOctalString = toMode . fileModeFromString . filter (`elem` ['0'..'7'])


fileModeFromString :: String -> FileMode
fileModeFromString [] = error "Cannot get Mode from empty string."
fileModeFromString s@[_] = error $ "Mode `" ++ s ++ "` not long enough."
fileModeFromString s@[_,_] = error $ "Mode `" ++ s ++ "` not long enough."
fileModeFromString [u,g,o] =
  fromIntegral $ digitToInt u * (8*8) .|. digitToInt g * 8 .|. digitToInt o
fileModeFromString [s,u,g,o] =
  fromIntegral $ digitToInt s * (8*8*8) .|. digitToInt u * (8*8) .|. digitToInt g * 8 .|. digitToInt o
fileModeFromString s = error $ "Mode `" ++ s ++ "` is too long"


toFileMode :: Mode -> FileMode
toFileMode (Mode u g o)
  = uFM u .|. gFM g .|. oFM o


toMode :: FileMode -> Mode
toMode fm = Mode ownerSet groupSet otherSet
  where
    convertSet f m =
      case find (\t -> m == f t) [O ..] of
        Nothing -> error $ "toMode: Unsupported mode: " ++ show m
        Just r  -> r
    ownerSet = convertSet uFM (fm .&. (System.Posix.ownerModes .|. uS))
    groupSet = convertSet gFM (fm .&. (System.Posix.groupModes .|. gS))
    otherSet = convertSet oFM (fm .&. (System.Posix.otherModes .|. oT))


----------------------------------------
--   ____       _            _        --
--  |  _ \ _ __(_)_   ____ _| |_ ___  --
--  | |_) | '__| \ \ / / _` | __/ _ \ --
--  |  __/| |  | |\ V / (_| | ||  __/ --
--  |_|   |_|  |_| \_/ \__,_|\__\___| --
----------------------------------------

uR, uW, uX, uS :: FileMode
uR = System.Posix.ownerReadMode
uW = System.Posix.ownerWriteMode
uX = System.Posix.ownerExecuteMode
uS = System.Posix.setUserIDMode


uFM :: ModeSet -> FileMode
uFM O    = System.Posix.nullFileMode
uFM R    = uR
uFM  W   =        uW
uFM   X  =               uX
uFM    S =                      uS
uFM RW   = uR .|. uW
uFM RX   = uR .|.        uX
uFM RS   = uR .|.               uS
uFM WX   =        uW .|. uX
uFM WS   =        uW .|.        uS
uFM XS   =               uX .|. uS
uFM RWX  = uR .|. uW .|. uX
uFM RWS  = uR .|. uW .|.        uS
uFM RXS  = uR .|.        uX .|. uS
uFM WXS  =        uW .|. uX .|. uS
uFM RWXS = uR .|. uW .|. uX .|. uS


gR, gW, gX, gS :: FileMode
gR = System.Posix.groupReadMode
gW = System.Posix.groupWriteMode
gX = System.Posix.groupExecuteMode
gS = System.Posix.setGroupIDMode

gFM :: ModeSet -> FileMode
gFM O    = System.Posix.nullFileMode
gFM R    = gR
gFM  W   =        gW
gFM   X  =               gX
gFM    S =                      gS
gFM RW   = gR .|. gW
gFM RX   = gR .|.        gX
gFM RS   = gR .|.               gS
gFM WX   =        gW .|. gX
gFM WS   =        gW .|.        gS
gFM XS   =               gX .|. gS
gFM RWX  = gR .|. gW .|. gX
gFM RWS  = gR .|. gW .|.        gS
gFM RXS  = gR .|.        gX .|. gS
gFM WXS  =        gW .|. gX .|. gS
gFM RWXS = gR .|. gW .|. gX .|. gS


oR, oW, oX, oT :: FileMode
oR = System.Posix.otherReadMode
oW = System.Posix.otherWriteMode
oX = System.Posix.otherExecuteMode
oT = (512)


oFM :: ModeSet -> FileMode
oFM O    = System.Posix.nullFileMode
oFM R    = oR
oFM  W   =        oW
oFM   X  =               oX
oFM    S =                      oT
oFM RW   = oR .|. oW
oFM RX   = oR .|.        oX
oFM RS   = oR .|.               oT
oFM WX   =        oW .|. oX
oFM WS   =        oW .|.        oT
oFM XS   =               oX .|. oT
oFM RWX  = oR .|. oW .|. oX
oFM RWS  = oR .|. oW .|.        oT
oFM RXS  = oR .|.        oX .|. oT
oFM WXS  =        oW .|. oX .|. oT
oFM RWXS = oR .|. oW .|. oX .|. oT


derive makeArbitrary ''ModeSet
derive makeArbitrary ''Mode