From c57f91fd0f9be252a65f179c63e37dce9b3498d5 Mon Sep 17 00:00:00 2001 From: Ranjeet Kumar Ranjan Date: Fri, 1 Jul 2022 09:48:00 +0530 Subject: [PATCH 1/6] Add Chmod module --- src/Streamly/Coreutils/Chmod.hs | 69 +++++++++++++++++++++++++++++++++ streamly-coreutils.cabal | 1 + 2 files changed, 70 insertions(+) create mode 100644 src/Streamly/Coreutils/Chmod.hs diff --git a/src/Streamly/Coreutils/Chmod.hs b/src/Streamly/Coreutils/Chmod.hs new file mode 100644 index 00000000..509b56c3 --- /dev/null +++ b/src/Streamly/Coreutils/Chmod.hs @@ -0,0 +1,69 @@ +-- | +-- Module : Streamly.Coreutils.Chmod +-- Copyright : (c) 2022 Composewell Technologies +-- License : BSD-3-Clause +-- Maintainer : streamly@composewell.com +-- Stability : experimental +-- Portability : GHC +-- +-- change file mode bits. + +module Streamly.Coreutils.Chmod + ( chmod + ) +where + +import Data.Bits ((.|.), Bits ((.&.), complement)) +import Data.Default.Class (Default(..)) + +import qualified System.Posix as Posix + +data UserType = Owner | Group | Others deriving (Eq, Ord, Read, Show) + +data Permissions = Permissions + { readable :: Bool + , writable :: Bool + , executable :: Bool + } deriving (Eq, Ord, Read, Show) + +instance Default Permissions where + def = Permissions + { readable = False + , writable = False + , executable = False + } + +modifyBit :: Bool -> Posix.FileMode -> Posix.FileMode -> Posix.FileMode +modifyBit False b m = m .&. complement b +modifyBit True b m = m .|. b + +chmod :: UserType -> Permissions -> FilePath -> IO () +chmod utype (Permissions r w e) path = do + case utype of + Owner -> do + stat <- Posix.getFileStatus path + Posix.setFileMode + path + ( modifyBit e Posix.ownerExecuteMode + . modifyBit w Posix.ownerWriteMode + . modifyBit r Posix.ownerReadMode + . Posix.fileMode $ stat + ) + Group -> do + stat <- Posix.getFileStatus path + Posix.setFileMode + path + ( modifyBit e Posix.groupExecuteMode + . modifyBit w Posix.groupWriteMode + . modifyBit r Posix.groupReadMode + . Posix.fileMode $ stat + ) + Others -> do + stat <- Posix.getFileStatus path + Posix.setFileMode + path + ( modifyBit e Posix.otherExecuteMode + . modifyBit w Posix.otherWriteMode + . modifyBit r Posix.otherReadMode + . Posix.fileMode $ stat + ) diff --git a/streamly-coreutils.cabal b/streamly-coreutils.cabal index 8394de69..afdb533d 100644 --- a/streamly-coreutils.cabal +++ b/streamly-coreutils.cabal @@ -109,6 +109,7 @@ library hs-source-dirs: src exposed-modules: Streamly.Coreutils + , Streamly.Coreutils.Chmod , Streamly.Coreutils.Common , Streamly.Coreutils.Cp , Streamly.Coreutils.Directory From 19811a4745b7e9bc4458bdc183b76e8dee1c69e9 Mon Sep 17 00:00:00 2001 From: Ranjeet Kumar Ranjan Date: Wed, 6 Jul 2022 11:14:10 +0530 Subject: [PATCH 2/6] Add Quasiquote support --- src/Streamly/Coreutils/Chmod.hs | 51 ++++++------ src/Streamly/Coreutils/StringQ.hs | 133 ++++++++++++++++++++++++++++++ streamly-coreutils.cabal | 4 + 3 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 src/Streamly/Coreutils/StringQ.hs diff --git a/src/Streamly/Coreutils/Chmod.hs b/src/Streamly/Coreutils/Chmod.hs index 509b56c3..5dc2f1a6 100644 --- a/src/Streamly/Coreutils/Chmod.hs +++ b/src/Streamly/Coreutils/Chmod.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE QuasiQuotes #-} -- | -- Module : Streamly.Coreutils.Chmod -- Copyright : (c) 2022 Composewell Technologies @@ -9,38 +10,29 @@ -- change file mode bits. module Streamly.Coreutils.Chmod - ( chmod + ( chmod ) where import Data.Bits ((.|.), Bits ((.&.), complement)) -import Data.Default.Class (Default(..)) - +import Streamly.Coreutils.StringQ import qualified System.Posix as Posix -data UserType = Owner | Group | Others deriving (Eq, Ord, Read, Show) - -data Permissions = Permissions - { readable :: Bool - , writable :: Bool - , executable :: Bool - } deriving (Eq, Ord, Read, Show) - -instance Default Permissions where - def = Permissions - { readable = False - , writable = False - , executable = False - } - modifyBit :: Bool -> Posix.FileMode -> Posix.FileMode -> Posix.FileMode modifyBit False b m = m .&. complement b modifyBit True b m = m .|. b -chmod :: UserType -> Permissions -> FilePath -> IO () -chmod utype (Permissions r w e) path = do +chmodWith :: UserType -> Permissions -> FilePath -> IO () +chmodWith utype (Permissions r w e) path = do case utype of - Owner -> do + Owner -> setOwnerPermissions + Group -> setGroupPermissions + Others -> setOthersPermissions + All -> setAllPermissions + + where + + setOwnerPermissions = do stat <- Posix.getFileStatus path Posix.setFileMode path @@ -49,7 +41,8 @@ chmod utype (Permissions r w e) path = do . modifyBit r Posix.ownerReadMode . Posix.fileMode $ stat ) - Group -> do + + setGroupPermissions = do stat <- Posix.getFileStatus path Posix.setFileMode path @@ -58,7 +51,8 @@ chmod utype (Permissions r w e) path = do . modifyBit r Posix.groupReadMode . Posix.fileMode $ stat ) - Others -> do + + setOthersPermissions = do stat <- Posix.getFileStatus path Posix.setFileMode path @@ -67,3 +61,14 @@ chmod utype (Permissions r w e) path = do . modifyBit r Posix.otherReadMode . Posix.fileMode $ stat ) + + setAllPermissions = do + setOwnerPermissions + setGroupPermissions + setOthersPermissions + +-- | Supports only override permissions bits +-- >> chmod [perm|a=rwx|] "a.txt" +-- +chmod :: UserTypePerm -> FilePath -> IO () +chmod pat = chmodWith (utype pat) (permssions pat) diff --git a/src/Streamly/Coreutils/StringQ.hs b/src/Streamly/Coreutils/StringQ.hs new file mode 100644 index 00000000..e89efc66 --- /dev/null +++ b/src/Streamly/Coreutils/StringQ.hs @@ -0,0 +1,133 @@ +{-# LANGUAGE TemplateHaskell #-} +-- | +-- Module : Streamly.Coreutils.StringQ +-- Copyright : (c) 2022 Composewell Technologies +-- License : BSD-3-Clause +-- Maintainer : streamly@composewell.com +-- Stability : experimental +-- Portability : GHC +-- +-- change file mode bits. + +module Streamly.Coreutils.StringQ + ( + perm + , UserType(..) + , Permissions(..) + , UserTypePerm(..) + ) +where + +import Control.Applicative (Alternative(..)) +import Control.Monad.Catch (MonadCatch) +import Control.Monad.IO.Class (liftIO, MonadIO) +import Data.Char (chr) +import Data.Data (Data, Typeable) +import Data.Default.Class (Default(..)) +import Language.Haskell.TH (Exp, Q, Pat) +import Language.Haskell.TH.Quote (QuasiQuoter(..), dataToExpQ, dataToPatQ) +import Streamly.Internal.Data.Parser (Parser) + +import qualified Streamly.Internal.Data.Fold as Fold +import qualified Streamly.Internal.Data.Parser as Parser +import qualified Streamly.Internal.Data.Stream.IsStream as Stream +import qualified Streamly.Internal.Unicode.Char.Parser as Parser + +strParser :: MonadCatch m => Parser m Char String +strParser = + let ut = Parser.char 'u' + <|> Parser.char 'g' + <|> Parser.char 'o' + <|> Parser.char 'a' + op = Parser.char '=' -- supports only override permissions bits + p1 = Parser.char (chr 0) + <|> Parser.char 'r' + <|> Parser.char 'w' + <|> Parser.char 'x' + r = ut *> op + r1 = ut *> op *> p1 + r2 = ut *> op *> p1 *> p1 + r3 = ut *> op *> p1 *> p1 *> p1 + s = r <|> r1 <|> r2 <|> r3 + in Parser.some s Fold.toList + +expandVars :: String -> IO () +expandVars ln = + case Stream.parse strParser (Stream.fromList ln) of + Left _ -> fail "Parsing of perm quoted string failed." + Right _ -> return () + +data Permissions = Permissions + { readable :: Bool + , writable :: Bool + , executable :: Bool + } deriving (Eq, Ord, Read, Show, Typeable, Data) + +data UserType = + Owner + | Group + | Others + | All + deriving (Eq, Ord, Read, Show, Typeable, Data) + +data UserTypePerm = + UserTypePerm + { utype :: UserType + , permssions :: Permissions + } deriving (Eq, Ord, Read, Show, Typeable, Data) + +instance Default Permissions where + def = Permissions + { readable = False + , writable = False + , executable = False + } + +parseExpr :: MonadIO m => String -> m UserTypePerm +parseExpr s = do + liftIO $ expandVars s + let ut = head s + bits = tail $ tail s + return $ + case ut of + 'u' -> UserTypePerm Owner $ setPermission bits + 'g' -> UserTypePerm Group $ setPermission bits + 'o' -> UserTypePerm Others $ setPermission bits + 'a' -> UserTypePerm All $ setPermission bits + _ -> error "Invalid permissions" + + where + + setPermission bits = + case bits of + "rwx" -> Permissions True True True + "rw" -> Permissions True True False + "r" -> Permissions True False False + "w" -> Permissions False True False + "x" -> Permissions False False True + "rx" -> Permissions True False True + "wx" -> Permissions False True True + _ -> def + +quoteExprExp :: String -> Q Exp +quoteExprExp s = do + expr <- parseExpr s + dataToExpQ (const Nothing) expr + +quoteExprPat :: String -> Q Pat +quoteExprPat s = do + expr <- parseExpr s + dataToPatQ (const Nothing) expr + +perm :: QuasiQuoter +perm = + QuasiQuoter + { quoteExp = quoteExprExp + , quotePat = quoteExprPat + , quoteType = notSupported + , quoteDec = notSupported + } + + where + + notSupported = error "perm: Not supported." diff --git a/streamly-coreutils.cabal b/streamly-coreutils.cabal index afdb533d..adf47fd1 100644 --- a/streamly-coreutils.cabal +++ b/streamly-coreutils.cabal @@ -106,11 +106,15 @@ library , unix >= 2.7.0 && < 2.8 , directory >= 1.2.2 && < 1.4 , filepath >= 1.4 && < 1.5 + , data-default-class >= 0.1 && < 0.2 + , template-haskell >= 2.10.0 && < 2.19.0 + hs-source-dirs: src exposed-modules: Streamly.Coreutils , Streamly.Coreutils.Chmod , Streamly.Coreutils.Common + , Streamly.Coreutils.StringQ , Streamly.Coreutils.Cp , Streamly.Coreutils.Directory , Streamly.Coreutils.Dirname From 4896d011e7d64a461834c873d56b5e961b1b06ef Mon Sep 17 00:00:00 2001 From: Ranjeet Kumar Ranjan Date: Wed, 27 Jul 2022 12:42:27 +0530 Subject: [PATCH 3/6] Fix warnings --- src/Streamly/Coreutils/StringQ.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Streamly/Coreutils/StringQ.hs b/src/Streamly/Coreutils/StringQ.hs index e89efc66..e1b0ace9 100644 --- a/src/Streamly/Coreutils/StringQ.hs +++ b/src/Streamly/Coreutils/StringQ.hs @@ -22,7 +22,7 @@ import Control.Applicative (Alternative(..)) import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class (liftIO, MonadIO) import Data.Char (chr) -import Data.Data (Data, Typeable) +import Data.Data (Data) import Data.Default.Class (Default(..)) import Language.Haskell.TH (Exp, Q, Pat) import Language.Haskell.TH.Quote (QuasiQuoter(..), dataToExpQ, dataToPatQ) @@ -61,20 +61,20 @@ data Permissions = Permissions { readable :: Bool , writable :: Bool , executable :: Bool - } deriving (Eq, Ord, Read, Show, Typeable, Data) + } deriving (Eq, Ord, Read, Show, Data) data UserType = Owner | Group | Others | All - deriving (Eq, Ord, Read, Show, Typeable, Data) + deriving (Eq, Ord, Read, Show, Data) data UserTypePerm = UserTypePerm { utype :: UserType , permssions :: Permissions - } deriving (Eq, Ord, Read, Show, Typeable, Data) + } deriving (Eq, Ord, Read, Show, Data) instance Default Permissions where def = Permissions From 3d45808a5822bb8d6bd66561010612457d5af03d Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 19 Apr 2023 18:19:02 +0530 Subject: [PATCH 4/6] Add design notes --- src/Streamly/Coreutils/Chmod.hs | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/Streamly/Coreutils/Chmod.hs b/src/Streamly/Coreutils/Chmod.hs index 5dc2f1a6..409ceef6 100644 --- a/src/Streamly/Coreutils/Chmod.hs +++ b/src/Streamly/Coreutils/Chmod.hs @@ -9,6 +9,47 @@ -- -- change file mode bits. +-- TODO: change this module to Chmod.Posix and later create a portable module. +-- +-- Design notes: +-- +-- On Posix systems: +-- +-- Roles: User (Owner), group (only one), others +-- Permissions: rwxX(ugo), s(go), t(o) +-- +-- 1. write: create or delete a file in a directory. Modify contents of a file. +-- 2. write: modify metadata of a directory or file. +-- 3. execute: to list a directory's contents +-- +-- On Windows: +-- +-- Could not find any good docs by microsoft on a google search. +-- Managing permissions: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc770962(v=ws.11) +-- https://learn.microsoft.com/en-us/windows/security/identity-protection/access-control/access-control +-- +-- Roles: User (Owner), group (many) +-- Permissions: read, read+execute, modify (metadata, create/delete files in +-- dirs), write (write to a file), list dir, full control +-- Inheritance: permissions can be inherited from parent directories +-- Advanced Permissions: ... +-- +-- 1. write: create or delete a file in a directory. Modify contents of a file. +-- 2. modify: modify metadata of a directory or file. +-- 3. list dir: to list a directory's contents +-- +-- Common abstraction for windows/posix: +-- +-- Roles: User/Owner +-- Permissions: +-- +-- 1. write on Posix: write+modify on windows +-- 2. execute on dir: "list dir" on windows +-- +-- Other's default permissions are controlled by umask on Posix. When setting +-- permissions we can ensure that other's permissions are less restrictive than +-- the owner? But we cannot do the same on windows. + module Streamly.Coreutils.Chmod ( chmod ) From e24dd87a2a45eb4cfaba70b70152c16838bc8cfd Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 19 Apr 2023 21:30:30 +0530 Subject: [PATCH 5/6] Refactor the code, add TODOs --- src/Streamly/Coreutils/Chmod.hs | 32 ++++++-- src/Streamly/Coreutils/StringQ.hs | 132 +++++++++++++++++++++--------- 2 files changed, 118 insertions(+), 46 deletions(-) diff --git a/src/Streamly/Coreutils/Chmod.hs b/src/Streamly/Coreutils/Chmod.hs index 409ceef6..e66227ee 100644 --- a/src/Streamly/Coreutils/Chmod.hs +++ b/src/Streamly/Coreutils/Chmod.hs @@ -51,7 +51,19 @@ -- the owner? But we cannot do the same on windows. module Streamly.Coreutils.Chmod - ( chmod + ( + -- * Roles + Role (..) + + -- * Permissions + , Permissions + , setReadable + , setWritable + , setExecutable + , reset + + -- * Chmod + , chmod ) where @@ -63,7 +75,7 @@ modifyBit :: Bool -> Posix.FileMode -> Posix.FileMode -> Posix.FileMode modifyBit False b m = m .&. complement b modifyBit True b m = m .|. b -chmodWith :: UserType -> Permissions -> FilePath -> IO () +chmodWith :: Role -> Permissions -> FilePath -> IO () chmodWith utype (Permissions r w e) path = do case utype of Owner -> setOwnerPermissions @@ -108,8 +120,16 @@ chmodWith utype (Permissions r w e) path = do setGroupPermissions setOthersPermissions --- | Supports only override permissions bits --- >> chmod [perm|a=rwx|] "a.txt" +-- | Change the file permission modes for specified roles using the specified +-- permission modifier functions. +-- +-- You can use the @mode@ quasiquoter to build the mode conveniently, for +-- example: +-- +-- >> chmod [mode|a=rwx|] "a.txt" -- -chmod :: UserTypePerm -> FilePath -> IO () -chmod pat = chmodWith (utype pat) (permssions pat) +chmod :: [(Role, Permissions -> Permissions)] -> FilePath -> IO () +-- To implement this, get the file mode. Transform the FileMode using the roles +-- and permissions, and then use a single setFileMode call to set the mode in +-- the end. +chmod pat = undefined diff --git a/src/Streamly/Coreutils/StringQ.hs b/src/Streamly/Coreutils/StringQ.hs index e1b0ace9..10b84bbc 100644 --- a/src/Streamly/Coreutils/StringQ.hs +++ b/src/Streamly/Coreutils/StringQ.hs @@ -9,12 +9,16 @@ -- -- change file mode bits. +-- XXX Rename to "Permissions" or "AccessControl" + module Streamly.Coreutils.StringQ ( - perm - , UserType(..) + Role(..) , Permissions(..) - , UserTypePerm(..) + , setReadable + , setWritable + , setExecutable + , reset ) where @@ -31,9 +35,63 @@ import Streamly.Internal.Data.Parser (Parser) import qualified Streamly.Internal.Data.Fold as Fold import qualified Streamly.Internal.Data.Parser as Parser import qualified Streamly.Internal.Data.Stream.IsStream as Stream -import qualified Streamly.Internal.Unicode.Char.Parser as Parser +import qualified Streamly.Internal.Unicode.Parser as Parser + +------------------------------------------------------------------------------- +-- Permissions +------------------------------------------------------------------------------- + +-- | Permissions for access control +data Permissions = Permissions + { readable :: Bool + , writable :: Bool + , executable :: Bool + -- , searchable :: Bool -- for portability, keep it separate + } deriving (Eq, Ord, Read, Show, Data) -strParser :: MonadCatch m => Parser m Char String +{- +defaultPermissions = + Permissions + { readable = False + , writable = False + , executable = False + } +-} + +-- | Enable @read@ permission. +setReadable :: Bool -> Permissions -> Permissions +setReadable x perms = perms { readable = x } + +-- | Enable @write@ permission. +setWritable :: Bool -> Permissions -> Permissions +setWritable x perms = perms { writable = x } + +-- | Enable @execute@ permission. +setExecutable :: Bool -> Permissions -> Permissions +setExecutable x perms = perms { executable = x } + +-- | Disable all permissions. +reset :: Permissions -> Permissions +reset = setReadable False . setWritable False . setExecutable False + +------------------------------------------------------------------------------- +-- Roles +------------------------------------------------------------------------------- + +-- | Roles to whom access is granted. +data Role = + Owner + | Group + | Others + | All + deriving (Eq, Ord, Read, Show, Data) + +------------------------------------------------------------------------------- +-- Mode parser +------------------------------------------------------------------------------- + +{- +strParser :: MonadCatch m => Parser Char m String strParser = let ut = Parser.char 'u' <|> Parser.char 'g' @@ -57,33 +115,7 @@ expandVars ln = Left _ -> fail "Parsing of perm quoted string failed." Right _ -> return () -data Permissions = Permissions - { readable :: Bool - , writable :: Bool - , executable :: Bool - } deriving (Eq, Ord, Read, Show, Data) - -data UserType = - Owner - | Group - | Others - | All - deriving (Eq, Ord, Read, Show, Data) - -data UserTypePerm = - UserTypePerm - { utype :: UserType - , permssions :: Permissions - } deriving (Eq, Ord, Read, Show, Data) - -instance Default Permissions where - def = Permissions - { readable = False - , writable = False - , executable = False - } - -parseExpr :: MonadIO m => String -> m UserTypePerm +parseExpr :: MonadIO m => String -> m [(Role, Permissions)] parseExpr s = do liftIO $ expandVars s let ut = head s @@ -119,15 +151,35 @@ quoteExprPat s = do expr <- parseExpr s dataToPatQ (const Nothing) expr -perm :: QuasiQuoter -perm = +-- TODO: perms can have a single letter from the set ugo, in that case the +-- existing permissions are copied from that role. + +-- When we get a "=" use 'reset', when we get a '+' use an operation with +-- argument True, else use False. + +-- | The format of a symbolic mode is [roles][-+=][perms...], where roles is +-- either zero or more letters from the set ugoa. perms is either zero or more +-- letters from the set rwxXst. Multiple symbolic modes can be given, separated +-- by commas. +-- +-- Examples: +-- +-- @ +-- - +-- -rwx +-- g-rx +-- g-x+r +-- go-x+rw +-- go-x+rw,u+r +-- @ +-- +-- If the role is omitted it is assumed to be 'a'. +mode :: QuasiQuoter +mode = QuasiQuoter { quoteExp = quoteExprExp , quotePat = quoteExprPat - , quoteType = notSupported - , quoteDec = notSupported + , quoteType = error "mode: quoteType not supported." + , quoteDec = error "mode: quoteDec not supported." } - - where - - notSupported = error "perm: Not supported." +-} From 81017602c460dea406784fb8375de658b62b55eb Mon Sep 17 00:00:00 2001 From: Ranjeet Kumar Ranjan Date: Thu, 20 Apr 2023 16:26:16 +0530 Subject: [PATCH 6/6] Fix TODOs --- src/Streamly/Coreutils/Chmod.hs | 135 ----------------- src/Streamly/Coreutils/Chmod/Posix.hs | 199 ++++++++++++++++++++++++++ src/Streamly/Coreutils/StringQ.hs | 1 - streamly-coreutils.cabal | 2 +- 4 files changed, 200 insertions(+), 137 deletions(-) delete mode 100644 src/Streamly/Coreutils/Chmod.hs create mode 100644 src/Streamly/Coreutils/Chmod/Posix.hs diff --git a/src/Streamly/Coreutils/Chmod.hs b/src/Streamly/Coreutils/Chmod.hs deleted file mode 100644 index e66227ee..00000000 --- a/src/Streamly/Coreutils/Chmod.hs +++ /dev/null @@ -1,135 +0,0 @@ -{-# LANGUAGE QuasiQuotes #-} --- | --- Module : Streamly.Coreutils.Chmod --- Copyright : (c) 2022 Composewell Technologies --- License : BSD-3-Clause --- Maintainer : streamly@composewell.com --- Stability : experimental --- Portability : GHC --- --- change file mode bits. - --- TODO: change this module to Chmod.Posix and later create a portable module. --- --- Design notes: --- --- On Posix systems: --- --- Roles: User (Owner), group (only one), others --- Permissions: rwxX(ugo), s(go), t(o) --- --- 1. write: create or delete a file in a directory. Modify contents of a file. --- 2. write: modify metadata of a directory or file. --- 3. execute: to list a directory's contents --- --- On Windows: --- --- Could not find any good docs by microsoft on a google search. --- Managing permissions: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc770962(v=ws.11) --- https://learn.microsoft.com/en-us/windows/security/identity-protection/access-control/access-control --- --- Roles: User (Owner), group (many) --- Permissions: read, read+execute, modify (metadata, create/delete files in --- dirs), write (write to a file), list dir, full control --- Inheritance: permissions can be inherited from parent directories --- Advanced Permissions: ... --- --- 1. write: create or delete a file in a directory. Modify contents of a file. --- 2. modify: modify metadata of a directory or file. --- 3. list dir: to list a directory's contents --- --- Common abstraction for windows/posix: --- --- Roles: User/Owner --- Permissions: --- --- 1. write on Posix: write+modify on windows --- 2. execute on dir: "list dir" on windows --- --- Other's default permissions are controlled by umask on Posix. When setting --- permissions we can ensure that other's permissions are less restrictive than --- the owner? But we cannot do the same on windows. - -module Streamly.Coreutils.Chmod - ( - -- * Roles - Role (..) - - -- * Permissions - , Permissions - , setReadable - , setWritable - , setExecutable - , reset - - -- * Chmod - , chmod - ) -where - -import Data.Bits ((.|.), Bits ((.&.), complement)) -import Streamly.Coreutils.StringQ -import qualified System.Posix as Posix - -modifyBit :: Bool -> Posix.FileMode -> Posix.FileMode -> Posix.FileMode -modifyBit False b m = m .&. complement b -modifyBit True b m = m .|. b - -chmodWith :: Role -> Permissions -> FilePath -> IO () -chmodWith utype (Permissions r w e) path = do - case utype of - Owner -> setOwnerPermissions - Group -> setGroupPermissions - Others -> setOthersPermissions - All -> setAllPermissions - - where - - setOwnerPermissions = do - stat <- Posix.getFileStatus path - Posix.setFileMode - path - ( modifyBit e Posix.ownerExecuteMode - . modifyBit w Posix.ownerWriteMode - . modifyBit r Posix.ownerReadMode - . Posix.fileMode $ stat - ) - - setGroupPermissions = do - stat <- Posix.getFileStatus path - Posix.setFileMode - path - ( modifyBit e Posix.groupExecuteMode - . modifyBit w Posix.groupWriteMode - . modifyBit r Posix.groupReadMode - . Posix.fileMode $ stat - ) - - setOthersPermissions = do - stat <- Posix.getFileStatus path - Posix.setFileMode - path - ( modifyBit e Posix.otherExecuteMode - . modifyBit w Posix.otherWriteMode - . modifyBit r Posix.otherReadMode - . Posix.fileMode $ stat - ) - - setAllPermissions = do - setOwnerPermissions - setGroupPermissions - setOthersPermissions - --- | Change the file permission modes for specified roles using the specified --- permission modifier functions. --- --- You can use the @mode@ quasiquoter to build the mode conveniently, for --- example: --- --- >> chmod [mode|a=rwx|] "a.txt" --- -chmod :: [(Role, Permissions -> Permissions)] -> FilePath -> IO () --- To implement this, get the file mode. Transform the FileMode using the roles --- and permissions, and then use a single setFileMode call to set the mode in --- the end. -chmod pat = undefined diff --git a/src/Streamly/Coreutils/Chmod/Posix.hs b/src/Streamly/Coreutils/Chmod/Posix.hs new file mode 100644 index 00000000..cc0d787d --- /dev/null +++ b/src/Streamly/Coreutils/Chmod/Posix.hs @@ -0,0 +1,199 @@ +{-# LANGUAGE QuasiQuotes #-} +-- | +-- Module : Streamly.Coreutils.Chmod.Posix +-- Copyright : (c) 2022 Composewell Technologies +-- License : BSD-3-Clause +-- Maintainer : streamly@composewell.com +-- Stability : experimental +-- Portability : GHC +-- +-- change file mode bits. + +-- TODO: change this module to Chmod.Posix and later create a portable module. +-- +-- Design notes: +-- +-- On Posix systems: +-- +-- Roles: User (Owner), group (only one), others +-- Permissions: rwxX(ugo), s(go), t(o) +-- +-- 1. write: create or delete a file in a directory. Modify contents of a file. +-- 2. write: modify metadata of a directory or file. +-- 3. execute: to list a directory's contents +-- +-- On Windows: +-- +-- Could not find any good docs by microsoft on a google search. +-- Managing permissions: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc770962(v=ws.11) +-- https://learn.microsoft.com/en-us/windows/security/identity-protection/access-control/access-control +-- +-- Roles: User (Owner), group (many) +-- Permissions: read, read+execute, modify (metadata, create/delete files in +-- dirs), write (write to a file), list dir, full control +-- Inheritance: permissions can be inherited from parent directories +-- Advanced Permissions: ... +-- +-- 1. write: create or delete a file in a directory. Modify contents of a file. +-- 2. modify: modify metadata of a directory or file. +-- 3. list dir: to list a directory's contents +-- +-- Common abstraction for windows/posix: +-- +-- Roles: User/Owner +-- Permissions: +-- +-- 1. write on Posix: write+modify on windows +-- 2. execute on dir: "list dir" on windows +-- +-- Other's default permissions are controlled by umask on Posix. When setting +-- permissions we can ensure that other's permissions are less restrictive than +-- the owner? But we cannot do the same on windows. + +module Streamly.Coreutils.Chmod.Posix + ( + -- * Roles + Role (..) + + -- * Permissions + , Permissions + , setReadable + , setWritable + , setExecutable + , reset + + -- * Chmod + , chmodWith + , chmod + ) +where + +import Data.Bits ((.|.), Bits ((.&.), complement)) +import Streamly.Coreutils.StringQ +import qualified System.Posix as Posix +import GHC.IO.Unsafe (unsafePerformIO) + +modifyBit :: Bool -> Posix.FileMode -> Posix.FileMode -> Posix.FileMode +modifyBit False b m = m .&. complement b +modifyBit True b m = m .|. b + +chmodWith :: Role -> Permissions -> FilePath -> IO () +chmodWith utype (Permissions r w e) path = do + case utype of + Owner -> setOwnerPermissions + Group -> setGroupPermissions + Others -> setOthersPermissions + + where + + setOwnerPermissions = do + stat <- Posix.getFileStatus path + Posix.setFileMode + path + ( modifyBit e Posix.ownerExecuteMode + . modifyBit w Posix.ownerWriteMode + . modifyBit r Posix.ownerReadMode + . Posix.fileMode $ stat + ) + + setGroupPermissions = do + stat <- Posix.getFileStatus path + Posix.setFileMode + path + ( modifyBit e Posix.groupExecuteMode + . modifyBit w Posix.groupWriteMode + . modifyBit r Posix.groupReadMode + . Posix.fileMode $ stat + ) + + setOthersPermissions = do + stat <- Posix.getFileStatus path + Posix.setFileMode + path + ( modifyBit e Posix.otherExecuteMode + . modifyBit w Posix.otherWriteMode + . modifyBit r Posix.otherReadMode + . Posix.fileMode $ stat + ) + + +setMode :: Role -> Permissions -> Posix.FileMode -> Posix.FileMode +setMode utype (Permissions r w e) mode = + case utype of + Owner -> setOwnerPermissions + Group -> setGroupPermissions + Others -> setOthersPermissions + + where + + setOwnerPermissions = + modifyBit e Posix.ownerExecuteMode + $ modifyBit w Posix.ownerWriteMode + $ modifyBit r Posix.ownerReadMode mode + + setGroupPermissions = + modifyBit e Posix.groupExecuteMode + $ modifyBit w Posix.groupWriteMode + $ modifyBit r Posix.groupReadMode mode + + setOthersPermissions = + modifyBit e Posix.otherExecuteMode + $ modifyBit w Posix.otherWriteMode + $ modifyBit r Posix.otherReadMode mode + +-- | Change the file permission modes for specified roles using the specified +-- permission modifier functions. +-- +-- You can use the @mode@ quasiquoter to build the mode conveniently, for +-- example: +-- +-- >> chmod [mode|a=rwx|] "a.txt" +-- +chmod :: [(Role, Permissions -> Permissions)] -> FilePath -> IO () +-- To implement this, get the file mode. Transform the FileMode using the roles +-- and permissions, and then use a single setFileMode call to set the mode in +-- the end. +chmod perms path = do + stat <- Posix.getFileStatus path + let fm = foldl tr (Posix.fileMode stat) perms + Posix.setFileMode path fm + return () + + where + + tr mode (role, f) = unsafePerformIO $ do + stat <- Posix.getFileStatus path + let perm = case role of + Owner -> uPerm stat + Group -> gPerm stat + Others -> oPerm stat + fperm = f perm + return $ setMode role fperm mode + + -- current permissions + uPerm stat = + Permissions + (Posix.fileMode stat .&. Posix.ownerReadMode + == Posix.ownerReadMode) + (Posix.fileMode stat .&. Posix.ownerWriteMode + == Posix.ownerWriteMode) + (Posix.fileMode stat .&. Posix.ownerExecuteMode + == Posix.ownerExecuteMode) + + gPerm stat = + Permissions + (Posix.fileMode stat .&. Posix.groupReadMode + == Posix.groupReadMode) + (Posix.fileMode stat .&. Posix.groupWriteMode + == Posix.groupWriteMode) + (Posix.fileMode stat .&. Posix.groupExecuteMode + == Posix.groupExecuteMode) + + oPerm stat = + Permissions + (Posix.fileMode stat .&. Posix.otherReadMode + == Posix.otherReadMode) + (Posix.fileMode stat .&. Posix.otherWriteMode + == Posix.otherWriteMode) + (Posix.fileMode stat .&. Posix.otherExecuteMode + == Posix.otherExecuteMode) diff --git a/src/Streamly/Coreutils/StringQ.hs b/src/Streamly/Coreutils/StringQ.hs index 10b84bbc..e14298d1 100644 --- a/src/Streamly/Coreutils/StringQ.hs +++ b/src/Streamly/Coreutils/StringQ.hs @@ -83,7 +83,6 @@ data Role = Owner | Group | Others - | All deriving (Eq, Ord, Read, Show, Data) ------------------------------------------------------------------------------- diff --git a/streamly-coreutils.cabal b/streamly-coreutils.cabal index adf47fd1..8832113b 100644 --- a/streamly-coreutils.cabal +++ b/streamly-coreutils.cabal @@ -112,7 +112,7 @@ library hs-source-dirs: src exposed-modules: Streamly.Coreutils - , Streamly.Coreutils.Chmod + , Streamly.Coreutils.Chmod.Posix , Streamly.Coreutils.Common , Streamly.Coreutils.StringQ , Streamly.Coreutils.Cp