git-annex/Utility.hs

152 lines
4.3 KiB
Haskell
Raw Normal View History

2010-10-10 04:18:10 +00:00
{- git-annex utility functions
2010-10-27 20:53:54 +00:00
-
- Copyright 2010 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
2010-10-10 04:18:10 +00:00
-}
2010-10-11 21:52:46 +00:00
module Utility (
hGetContentsStrict,
2010-12-03 04:33:41 +00:00
readFileStrict,
2010-10-15 20:09:30 +00:00
parentDir,
absPath,
2010-10-15 20:09:30 +00:00
relPathCwdToDir,
2010-10-19 05:45:45 +00:00
relPathDirToDir,
boolSystem,
2010-11-08 21:44:08 +00:00
shellEscape,
unsetFileMode,
readMaybe,
safeWriteFile
2010-10-11 21:52:46 +00:00
) where
2010-10-10 04:18:10 +00:00
import System.IO
2010-10-19 05:45:45 +00:00
import System.Exit
import System.Posix.Process
2010-10-19 05:45:45 +00:00
import System.Posix.Signals
2010-11-08 21:44:08 +00:00
import System.Posix.Files
import System.Posix.Types
2010-10-10 04:18:10 +00:00
import Data.String.Utils
2010-10-15 20:09:30 +00:00
import System.Path
import System.FilePath
2010-10-15 20:09:30 +00:00
import System.Directory
2010-11-08 21:44:08 +00:00
import Foreign (complement)
2010-10-10 04:18:10 +00:00
2010-10-10 06:22:35 +00:00
{- A version of hgetContents that is not lazy. Ensures file is
- all read before it gets closed. -}
2010-10-31 20:00:32 +00:00
hGetContentsStrict :: Handle -> IO String
2010-10-10 06:22:35 +00:00
hGetContentsStrict h = hGetContents h >>= \s -> length s `seq` return s
2010-12-03 04:33:41 +00:00
{- A version of readFile that is not lazy. -}
readFileStrict :: FilePath -> IO String
readFileStrict f = readFile f >>= \s -> length s `seq` return s
2010-10-10 04:18:10 +00:00
{- Returns the parent directory of a path. Parent of / is "" -}
parentDir :: String -> String
parentDir dir =
2010-11-22 21:51:55 +00:00
if not $ null dirs
then slash ++ join s (take (length dirs - 1) dirs)
2010-10-10 04:18:10 +00:00
else ""
where
2010-11-22 21:51:55 +00:00
dirs = filter (not . null) $ split s dir
slash = if isAbsolute dir then s else ""
s = [pathSeparator]
2010-10-15 20:09:30 +00:00
{- Converts a filename into a normalized, absolute path. -}
absPath :: FilePath -> IO FilePath
absPath file = do
cwd <- getCurrentDirectory
case absNormPath cwd file of
Just f -> return f
Nothing -> error $ "unable to normalize " ++ file
2010-10-15 20:09:30 +00:00
{- Constructs a relative path from the CWD to a directory.
-
- For example, assuming CWD is /tmp/foo/bar:
- relPathCwdToDir "/tmp/foo" == "../"
- relPathCwdToDir "/tmp/foo/bar" == ""
-}
relPathCwdToDir :: FilePath -> IO FilePath
relPathCwdToDir dir = do
cwd <- getCurrentDirectory
a <- absPath dir
return $ relPathDirToDir cwd a
2010-10-15 20:09:30 +00:00
{- Constructs a relative path from one directory to another.
-
- Both directories must be absolute, and normalized (eg with absNormpath).
-
- The path will end with "/", unless it is empty.
-}
2010-10-15 20:09:30 +00:00
relPathDirToDir :: FilePath -> FilePath -> FilePath
relPathDirToDir from to =
2010-11-22 21:51:55 +00:00
if not $ null path
then addTrailingPathSeparator path
2010-10-15 20:09:30 +00:00
else ""
where
s = [pathSeparator]
pfrom = split s from
pto = split s to
2010-10-15 20:09:30 +00:00
common = map fst $ filter same $ zip pfrom pto
same (c,d) = c == d
uncommon = drop numcommon pto
2010-11-22 21:51:55 +00:00
dotdots = replicate (length pfrom - numcommon) ".."
numcommon = length common
path = join s $ dotdots ++ uncommon
2010-10-19 05:45:45 +00:00
{- Run a system command, and returns True or False
- if it succeeded or failed.
-
- SIGINT(ctrl-c) is allowed to propigate and will terminate the program.
2010-10-19 05:45:45 +00:00
-}
boolSystem :: FilePath -> [String] -> IO Bool
boolSystem command params = do
-- Going low-level because all the high-level system functions
-- block SIGINT etc. We need to block SIGCHLD, but allow
-- SIGINT to do its default program termination.
let sigset = addSignal sigCHLD emptySignalSet
oldint <- installHandler sigINT Default Nothing
oldset <- getSignalMask
blockSignals sigset
childpid <- forkProcess $ childaction oldint oldset
mps <- getProcessStatus True False childpid
restoresignals oldint oldset
case mps of
Just (Exited ExitSuccess) -> return True
_ -> return False
where
restoresignals oldint oldset = do
2010-10-31 20:00:32 +00:00
_ <- installHandler sigINT oldint Nothing
setSignalMask oldset
childaction oldint oldset = do
restoresignals oldint oldset
executeFile command True params Nothing
{- Escapes a filename to be safely able to be exposed to the shell. -}
2010-11-01 02:20:07 +00:00
shellEscape :: FilePath -> String
2010-11-01 02:22:01 +00:00
shellEscape f = "'" ++ escaped ++ "'"
where
-- replace ' with '"'"'
2010-11-01 02:22:01 +00:00
escaped = join "'\"'\"'" $ split "'" f
2010-11-08 21:44:08 +00:00
{- Removes a FileMode from a file.
- For example, call with otherWriteMode to chmod o-w -}
unsetFileMode :: FilePath -> FileMode -> IO ()
unsetFileMode f m = do
s <- getFileStatus f
2010-11-22 21:51:55 +00:00
setFileMode f $ fileMode s `intersectFileModes` complement m
{- Attempts to read a value from a String. -}
readMaybe :: (Read a) => String -> Maybe a
readMaybe s = case reads s of
((x,_):_) -> Just x
_ -> Nothing
{- Writes a file using a temp file that is renamed atomically into place. -}
safeWriteFile :: FilePath -> String -> IO ()
safeWriteFile file content = do
pid <- getProcessID
let tmpfile = file ++ ".tmp" ++ show pid
createDirectoryIfMissing True (parentDir file)
writeFile tmpfile content
renameFile tmpfile file