2011-09-23 22:13:24 +00:00
|
|
|
{- File mode utilities.
|
|
|
|
-
|
2023-04-27 19:57:50 +00:00
|
|
|
- Copyright 2010-2023 Joey Hess <id@joeyh.name>
|
2011-09-23 22:13:24 +00:00
|
|
|
-
|
2014-05-10 14:01:27 +00:00
|
|
|
- License: BSD-2-clause
|
2011-09-23 22:13:24 +00:00
|
|
|
-}
|
|
|
|
|
2013-05-10 21:29:59 +00:00
|
|
|
{-# LANGUAGE CPP #-}
|
2020-09-07 19:10:09 +00:00
|
|
|
{-# OPTIONS_GHC -fno-warn-tabs #-}
|
2013-05-10 21:29:59 +00:00
|
|
|
|
2015-10-08 18:26:21 +00:00
|
|
|
module Utility.FileMode (
|
2015-10-12 19:08:17 +00:00
|
|
|
module Utility.FileMode,
|
2015-10-08 18:26:21 +00:00
|
|
|
FileMode,
|
|
|
|
) where
|
2011-09-23 22:13:24 +00:00
|
|
|
|
2014-03-30 22:43:05 +00:00
|
|
|
import System.IO
|
|
|
|
import Control.Monad
|
2013-05-11 16:16:47 +00:00
|
|
|
import System.PosixCompat.Types
|
2023-03-27 16:17:55 +00:00
|
|
|
import System.PosixCompat.Files (unionFileModes, intersectFileModes, stdFileMode, nullFileMode, groupReadMode, ownerReadMode, ownerWriteMode, ownerExecuteMode, groupWriteMode, groupExecuteMode, otherReadMode, otherWriteMode, otherExecuteMode, fileMode)
|
2023-03-21 22:22:41 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
import System.PosixCompat.Files (setFileCreationMask)
|
|
|
|
#endif
|
2020-09-02 18:59:35 +00:00
|
|
|
import Control.Monad.IO.Class
|
2011-09-23 22:13:24 +00:00
|
|
|
import Foreign (complement)
|
2015-11-12 22:03:49 +00:00
|
|
|
import Control.Monad.Catch
|
2011-09-23 22:13:24 +00:00
|
|
|
|
2014-03-30 22:43:05 +00:00
|
|
|
import Utility.Exception
|
2020-11-05 22:45:37 +00:00
|
|
|
import Utility.FileSystemEncoding
|
|
|
|
import qualified Utility.RawFilePath as R
|
2014-03-30 22:43:05 +00:00
|
|
|
|
2012-04-21 18:06:36 +00:00
|
|
|
{- Applies a conversion function to a file's mode. -}
|
2020-11-05 22:45:37 +00:00
|
|
|
modifyFileMode :: RawFilePath -> (FileMode -> FileMode) -> IO ()
|
2015-12-16 00:42:35 +00:00
|
|
|
modifyFileMode f convert = void $ modifyFileMode' f convert
|
|
|
|
|
2020-11-05 22:45:37 +00:00
|
|
|
modifyFileMode' :: RawFilePath -> (FileMode -> FileMode) -> IO FileMode
|
2015-12-16 00:42:35 +00:00
|
|
|
modifyFileMode' f convert = do
|
2020-11-05 22:45:37 +00:00
|
|
|
s <- R.getFileStatus f
|
2012-04-21 18:06:36 +00:00
|
|
|
let old = fileMode s
|
|
|
|
let new = convert old
|
|
|
|
when (new /= old) $
|
2020-11-05 22:45:37 +00:00
|
|
|
R.setFileMode f new
|
2015-12-16 00:42:35 +00:00
|
|
|
return old
|
|
|
|
|
|
|
|
{- Runs an action after changing a file's mode, then restores the old mode. -}
|
2020-11-05 22:45:37 +00:00
|
|
|
withModifiedFileMode :: RawFilePath -> (FileMode -> FileMode) -> IO a -> IO a
|
2015-12-16 00:42:35 +00:00
|
|
|
withModifiedFileMode file convert a = bracket setup cleanup go
|
|
|
|
where
|
|
|
|
setup = modifyFileMode' file convert
|
|
|
|
cleanup oldmode = modifyFileMode file (const oldmode)
|
|
|
|
go _ = a
|
2012-04-21 18:06:36 +00:00
|
|
|
|
2012-04-21 20:01:56 +00:00
|
|
|
{- Adds the specified FileModes to the input mode, leaving the rest
|
|
|
|
- unchanged. -}
|
|
|
|
addModes :: [FileMode] -> FileMode -> FileMode
|
|
|
|
addModes ms m = combineModes (m:ms)
|
|
|
|
|
|
|
|
{- Removes the specified FileModes from the input mode. -}
|
|
|
|
removeModes :: [FileMode] -> FileMode -> FileMode
|
|
|
|
removeModes ms m = m `intersectFileModes` complement (combineModes ms)
|
|
|
|
|
|
|
|
writeModes :: [FileMode]
|
|
|
|
writeModes = [ownerWriteMode, groupWriteMode, otherWriteMode]
|
|
|
|
|
|
|
|
readModes :: [FileMode]
|
|
|
|
readModes = [ownerReadMode, groupReadMode, otherReadMode]
|
2011-09-23 22:13:24 +00:00
|
|
|
|
2012-11-09 16:51:54 +00:00
|
|
|
executeModes :: [FileMode]
|
|
|
|
executeModes = [ownerExecuteMode, groupExecuteMode, otherExecuteMode]
|
|
|
|
|
2014-04-01 00:15:16 +00:00
|
|
|
otherGroupModes :: [FileMode]
|
|
|
|
otherGroupModes =
|
|
|
|
[ groupReadMode, otherReadMode
|
|
|
|
, groupWriteMode, otherWriteMode
|
2018-12-19 15:56:39 +00:00
|
|
|
, groupExecuteMode, otherExecuteMode
|
2014-04-01 00:15:16 +00:00
|
|
|
]
|
|
|
|
|
2011-09-23 22:13:24 +00:00
|
|
|
{- Removes the write bits from a file. -}
|
2020-11-05 22:45:37 +00:00
|
|
|
preventWrite :: RawFilePath -> IO ()
|
2012-04-21 20:01:56 +00:00
|
|
|
preventWrite f = modifyFileMode f $ removeModes writeModes
|
2011-09-23 22:13:24 +00:00
|
|
|
|
2012-04-21 20:01:56 +00:00
|
|
|
{- Turns a file's owner write bit back on. -}
|
2020-11-05 22:45:37 +00:00
|
|
|
allowWrite :: RawFilePath -> IO ()
|
2012-04-21 20:01:56 +00:00
|
|
|
allowWrite f = modifyFileMode f $ addModes [ownerWriteMode]
|
2011-09-28 20:43:10 +00:00
|
|
|
|
2013-11-20 17:42:13 +00:00
|
|
|
{- Turns a file's owner read bit back on. -}
|
2020-11-05 22:45:37 +00:00
|
|
|
allowRead :: RawFilePath -> IO ()
|
2013-11-20 17:42:13 +00:00
|
|
|
allowRead f = modifyFileMode f $ addModes [ownerReadMode]
|
|
|
|
|
2012-04-21 18:06:36 +00:00
|
|
|
{- Allows owner and group to read and write to a file. -}
|
2013-11-18 22:05:37 +00:00
|
|
|
groupSharedModes :: [FileMode]
|
|
|
|
groupSharedModes =
|
2012-04-21 20:01:56 +00:00
|
|
|
[ ownerWriteMode, groupWriteMode
|
2012-04-21 18:06:36 +00:00
|
|
|
, ownerReadMode, groupReadMode
|
|
|
|
]
|
|
|
|
|
2020-11-05 22:45:37 +00:00
|
|
|
groupWriteRead :: RawFilePath -> IO ()
|
2013-11-18 22:05:37 +00:00
|
|
|
groupWriteRead f = modifyFileMode f $ addModes groupSharedModes
|
|
|
|
|
2012-09-25 17:30:32 +00:00
|
|
|
checkMode :: FileMode -> FileMode -> Bool
|
|
|
|
checkMode checkfor mode = checkfor `intersectFileModes` mode == checkfor
|
|
|
|
|
2012-03-14 16:17:38 +00:00
|
|
|
{- Checks if a file has any executable bits set. -}
|
|
|
|
isExecutable :: FileMode -> Bool
|
2012-11-09 16:51:54 +00:00
|
|
|
isExecutable mode = combineModes executeModes `intersectFileModes` mode /= 0
|
2012-04-21 20:01:56 +00:00
|
|
|
|
2023-04-27 19:57:50 +00:00
|
|
|
data ModeSetter = ModeSetter FileMode (RawFilePath -> IO ())
|
|
|
|
|
|
|
|
{- Runs an action which should create the file, passing it the desired
|
|
|
|
- initial file mode. Then runs the ModeSetter's action on the file, which
|
|
|
|
- can adjust the initial mode if umask prevented the file from being
|
|
|
|
- created with the right mode. -}
|
|
|
|
applyModeSetter :: Maybe ModeSetter -> RawFilePath -> (Maybe FileMode -> IO a) -> IO a
|
|
|
|
applyModeSetter (Just (ModeSetter mode modeaction)) file a = do
|
|
|
|
r <- a (Just mode)
|
|
|
|
void $ tryIO $ modeaction file
|
|
|
|
return r
|
|
|
|
applyModeSetter Nothing _ a =
|
|
|
|
a Nothing
|
2014-03-14 17:37:58 +00:00
|
|
|
|
2015-11-12 22:03:49 +00:00
|
|
|
withUmask :: (MonadIO m, MonadMask m) => FileMode -> m a -> m a
|
2014-03-14 17:37:58 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
withUmask umask a = bracket setup cleanup go
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
2015-11-12 22:03:49 +00:00
|
|
|
setup = liftIO $ setFileCreationMask umask
|
|
|
|
cleanup = liftIO . setFileCreationMask
|
2012-12-13 04:24:19 +00:00
|
|
|
go _ = a
|
2013-05-10 21:29:59 +00:00
|
|
|
#else
|
2014-03-14 17:37:58 +00:00
|
|
|
withUmask _ a = a
|
2013-05-10 21:29:59 +00:00
|
|
|
#endif
|
2012-04-21 20:59:49 +00:00
|
|
|
|
2017-02-13 21:30:28 +00:00
|
|
|
getUmask :: IO FileMode
|
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
getUmask = bracket setup cleanup return
|
|
|
|
where
|
|
|
|
setup = setFileCreationMask nullFileMode
|
|
|
|
cleanup = setFileCreationMask
|
|
|
|
#else
|
|
|
|
getUmask = return nullFileMode
|
|
|
|
#endif
|
|
|
|
|
|
|
|
defaultFileMode :: IO FileMode
|
|
|
|
defaultFileMode = do
|
|
|
|
umask <- getUmask
|
|
|
|
return $ intersectFileModes (complement umask) stdFileMode
|
|
|
|
|
2012-04-21 20:01:56 +00:00
|
|
|
combineModes :: [FileMode] -> FileMode
|
2015-04-29 18:15:08 +00:00
|
|
|
combineModes [] = 0
|
2012-04-21 20:01:56 +00:00
|
|
|
combineModes [m] = m
|
|
|
|
combineModes (m:ms) = foldl unionFileModes m ms
|
2013-05-11 16:16:47 +00:00
|
|
|
|
|
|
|
isSticky :: FileMode -> Bool
|
2013-08-02 16:27:32 +00:00
|
|
|
#ifdef mingw32_HOST_OS
|
2013-05-11 16:16:47 +00:00
|
|
|
isSticky _ = False
|
2013-05-10 21:29:59 +00:00
|
|
|
#else
|
2013-05-11 16:16:47 +00:00
|
|
|
isSticky = checkMode stickyMode
|
2012-09-25 17:30:32 +00:00
|
|
|
|
|
|
|
stickyMode :: FileMode
|
|
|
|
stickyMode = 512
|
|
|
|
|
2020-11-05 22:45:37 +00:00
|
|
|
setSticky :: RawFilePath -> IO ()
|
2012-09-25 17:30:32 +00:00
|
|
|
setSticky f = modifyFileMode f $ addModes [stickyMode]
|
2013-05-11 20:03:00 +00:00
|
|
|
#endif
|
2013-01-03 22:50:30 +00:00
|
|
|
|
|
|
|
{- Writes a file, ensuring that its modes do not allow it to be read
|
2014-03-14 17:37:58 +00:00
|
|
|
- or written by anyone other than the current user,
|
|
|
|
- before any content is written.
|
|
|
|
-
|
|
|
|
- When possible, this is done using the umask.
|
2013-05-03 03:34:58 +00:00
|
|
|
-
|
|
|
|
- On a filesystem that does not support file permissions, this is the same
|
|
|
|
- as writeFile.
|
|
|
|
-}
|
2020-11-05 22:45:37 +00:00
|
|
|
writeFileProtected :: RawFilePath -> String -> IO ()
|
2015-04-28 18:58:29 +00:00
|
|
|
writeFileProtected file content = writeFileProtected' file
|
|
|
|
(\h -> hPutStr h content)
|
|
|
|
|
2020-11-05 22:45:37 +00:00
|
|
|
writeFileProtected' :: RawFilePath -> (Handle -> IO ()) -> IO ()
|
2023-10-20 17:19:12 +00:00
|
|
|
writeFileProtected' file writer = bracket setup cleanup writer
|
|
|
|
where
|
|
|
|
setup = do
|
|
|
|
h <- protectedOutput $ openFile (fromRawFilePath file) WriteMode
|
|
|
|
void $ tryIO $ modifyFileMode file $ removeModes otherGroupModes
|
|
|
|
return h
|
|
|
|
cleanup = hClose
|
2017-03-30 23:32:58 +00:00
|
|
|
|
|
|
|
protectedOutput :: IO a -> IO a
|
|
|
|
protectedOutput = withUmask 0o0077
|