packages feed

propellor 2.15.0 → 2.15.1

raw patch · 31 files changed

+260/−95 lines, 31 files

Files

CHANGELOG view
@@ -1,3 +1,17 @@+propellor (2.15.1) unstable; urgency=medium++  * Added git configs propellor.spin-branch and propellor.forbid-dirty-spin.+    Thanks, Sean Whitton.+  * Added User.systemAccountFor and User.systemAccountFor' properties.+    Thanks, Félix Sipma.+  * Gpg.keyImported converted to not use a flag file and instead check+    if gpg has the provided key already.+    Thanks, Félix Sipma.+  * Merged Utility changes from git-annex.+  * Clean build with ghc 7.10.++ -- Joey Hess <id@joeyh.name>  Sat, 19 Dec 2015 16:43:09 -0400+ propellor (2.15.0) unstable; urgency=medium    * Added UncheckedProperty type, along with unchecked to indicate a
debian/changelog view
@@ -1,3 +1,17 @@+propellor (2.15.1) unstable; urgency=medium++  * Added git configs propellor.spin-branch and propellor.forbid-dirty-spin.+    Thanks, Sean Whitton.+  * Added User.systemAccountFor and User.systemAccountFor' properties.+    Thanks, Félix Sipma.+  * Gpg.keyImported converted to not use a flag file and instead check+    if gpg has the provided key already.+    Thanks, Félix Sipma.+  * Merged Utility changes from git-annex.+  * Clean build with ghc 7.10.++ -- Joey Hess <id@joeyh.name>  Sat, 19 Dec 2015 16:43:09 -0400+ propellor (2.15.0) unstable; urgency=medium    * Added UncheckedProperty type, along with unchecked to indicate a
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 2.15.0+Version: 2.15.1 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>
src/Propellor/Debug.hs view
@@ -1,6 +1,5 @@ module Propellor.Debug where -import Control.Applicative import Control.Monad.IfElse import System.IO import System.Directory@@ -8,6 +7,8 @@ import System.Log.Formatter import System.Log.Handler (setFormatter) import System.Log.Handler.Simple+import Control.Applicative+import Prelude  import Utility.Monad import Utility.Env
src/Propellor/Engine.hs view
@@ -14,12 +14,13 @@ import System.Exit import System.IO import Data.Monoid-import Control.Applicative import "mtl" Control.Monad.RWS.Strict import System.PosixCompat import System.Posix.IO import System.FilePath import System.Directory+import Control.Applicative+import Prelude  import Propellor.Types import Propellor.Message
src/Propellor/Git.hs view
@@ -29,17 +29,31 @@ 	void $ boolSystem "git" [Param "config", Param (branchval "remote"), Param "origin"] 	void $ boolSystem "git" [Param "config", Param (branchval "merge"), Param $ "refs/heads/"++branch] +getGitConfigValue :: String -> IO (Maybe String)+getGitConfigValue key = do+	value <- catchMaybeIO $+		takeWhile (/= '\n')+			<$> readProcess "git" ["config", key]+	return $ case value of+		Just v | not (null v) -> Just v+		_ -> Nothing++-- `git config --bool propellor.blah` outputs "false" if propellor.blah is unset+-- i.e. the git convention is that the default value of any git-config setting+-- is "false".  So we don't need a Maybe Bool here.+getGitConfigBool :: String -> IO Bool+getGitConfigBool key = do+	value <- catchMaybeIO $+		takeWhile (/= '\n')+			<$> readProcess "git" ["config", "--bool", key]+	return $ case value of+		Just "true" -> True+		_ -> False+ getRepoUrl :: IO (Maybe String)-getRepoUrl = getM get urls+getRepoUrl = getM getGitConfigValue urls   where 	urls = ["remote.deploy.url", "remote.origin.url"]-	get u = do-		v <- catchMaybeIO $ -			takeWhile (/= '\n') -				<$> readProcess "git" ["config", u]-		return $ case v of-			Just url | not (null url) -> Just url-			_ -> Nothing  hasOrigin :: IO Bool hasOrigin = catchDefaultIO False $ do
src/Propellor/Gpg.hs view
@@ -1,6 +1,5 @@ module Propellor.Gpg where -import Control.Applicative import System.IO import System.FilePath import System.Directory@@ -9,6 +8,8 @@ import Control.Monad import System.Console.Concurrent import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..))+import Control.Applicative+import Prelude  import Propellor.PrivData.Paths import Propellor.Message@@ -79,12 +80,12 @@ 	]   where 	rmkeyring = boolSystem "gpg" $-		(map Param useKeyringOpts) ++ +		(map Param useKeyringOpts) ++ 		[ Param "--batch" 		, Param "--yes" 		, Param "--delete-key", Param keyid 		]-	+ 	gitconfig = ifM ((==) (keyid++"\n", True) <$> processTranscript "git" ["config", "user.signingkey"] Nothing) 		( boolSystem "git" 			[ Param "config"@@ -92,7 +93,7 @@ 			, Param "user.signingkey" 			] 		, return True-		)	+		)  reencryptPrivData :: IO Bool reencryptPrivData = ifM (doesFileExist privDataFile)@@ -101,7 +102,7 @@ 		gitAdd privDataFile 	, return True 	)-	+ gitAdd :: FilePath -> IO Bool gitAdd f = boolSystem "git" 	[ Param "add"@@ -125,7 +126,7 @@ -- Automatically sign the commit if there'a a keyring. gitCommit :: Maybe String -> [CommandParam] -> IO Bool gitCommit msg ps = do-	let ps' = Param "commit" : ps ++ +	let ps' = Param "commit" : ps ++ 		maybe [] (\m -> [Param "-m", Param m]) msg 	ps'' <- gpgSignParams ps' 	if isNothing msg
src/Propellor/Info.hs view
@@ -11,6 +11,7 @@ import Data.Maybe import Data.Monoid import Control.Applicative+import Prelude  pureInfoProperty :: (IsInfo v) => Desc -> v -> Property HasInfo pureInfoProperty desc v = pureInfoProperty' desc (addInfo mempty v)
src/Propellor/Message.hs view
@@ -22,10 +22,11 @@ import System.Console.ANSI import System.IO import Control.Monad.IO.Class (liftIO, MonadIO)-import Control.Applicative import System.IO.Unsafe (unsafePerformIO) import Control.Concurrent import System.Console.Concurrent+import Control.Applicative+import Prelude  import Propellor.Types import Utility.PartialPrelude@@ -41,7 +42,7 @@ globalMessageHandle :: MVar MessageHandle globalMessageHandle = unsafePerformIO $  	newMVar =<< MessageHandle-		<$> hIsTerminalDevice stdout+		<$> catchDefaultIO False (hIsTerminalDevice stdout)  -- | Gets the global MessageHandle. getMessageHandle :: IO MessageHandle
src/Propellor/PrivData.hs view
@@ -23,11 +23,9 @@ 	forceHostContext, ) where -import Control.Applicative import System.IO import System.Directory import Data.Maybe-import Data.Monoid import Data.List import Data.Typeable import Control.Monad@@ -38,6 +36,9 @@ import qualified Data.ByteString.Lazy as L import System.Console.Concurrent import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..))+import Control.Applicative+import Data.Monoid+import Prelude  import Propellor.Types import Propellor.Types.PrivData
src/Propellor/Property.hs view
@@ -38,12 +38,13 @@ import System.Directory import System.FilePath import Control.Monad-import Control.Applicative import Data.Monoid import Control.Monad.IfElse import "mtl" Control.Monad.RWS.Strict import System.Posix.Files import qualified Data.Hash.MD5 as MD5+import Control.Applicative+import Prelude  import Propellor.Types import Propellor.Types.ResultCheck
src/Propellor/Property/Apt.hs view
@@ -3,10 +3,11 @@ module Propellor.Property.Apt where  import Data.Maybe-import Control.Applicative import Data.List import System.IO import Control.Monad+import Control.Applicative+import Prelude  import Propellor.Base import qualified Propellor.Property.File as File
src/Propellor/Property/Chroot/Util.hs view
@@ -6,8 +6,9 @@ import Utility.Env import Utility.Directory -import Control.Applicative import System.Directory+import Control.Applicative+import Prelude  -- | When chrooting, it's useful to ensure that PATH has all the standard -- directories in it. This adds those directories to whatever PATH is
src/Propellor/Property/Cmd.hs view
@@ -44,9 +44,10 @@ 	waitForProcess, ) where -import Control.Applicative import Data.List import "mtl" Control.Monad.Reader+import Control.Applicative+import Prelude  import Propellor.Types import Propellor.Property
src/Propellor/Property/File.hs view
@@ -4,7 +4,6 @@ import Utility.FileMode  import System.Posix.Files-import System.PosixCompat.Types import System.Exit  type Line = String
src/Propellor/Property/Gpg.hs view
@@ -12,6 +12,8 @@ -- A numeric id, or a description of the key, in a form understood by gpg. newtype GpgKeyId = GpgKeyId { getGpgKeyId :: String } +data GpgKeyType = GpgPubKey | GpgPrivKey+ -- | Sets up a user with a gpg key from the privdata. -- -- Note that if a secret key is exported using gpg -a --export-secret-key,@@ -21,21 +23,38 @@ -- Recommend only using this for low-value dedicated role keys. -- No attempt has been made to scrub the key out of memory once it's used. keyImported :: GpgKeyId -> User -> Property HasInfo-keyImported (GpgKeyId keyid) user@(User u) = flagFile' prop genflag+keyImported key@(GpgKeyId keyid) user@(User u) = prop 	`requires` installed   where 	desc = u ++ " has gpg key " ++ show keyid-	genflag = do-		d <- dotDir user-		return $ d </> ".propellor-imported-keyid-" ++ keyid 	prop = withPrivData src (Context keyid) $ \getkey ->-		property desc $ getkey $ \key -> makeChange $-			withHandle StdinHandle createProcessSuccess-				(proc "su" ["-c", "gpg --import", u]) $ \h -> do-					fileEncoding h-					hPutStr h (unlines (privDataLines key))-					hClose h+		property desc $ getkey $ \key' -> do+			let keylines = privDataLines key'+			ifM (liftIO $ hasGpgKey (parse keylines))+				( return NoChange+				, makeChange $ withHandle StdinHandle createProcessSuccess+					(proc "su" ["-c", "gpg --import", u]) $ \h -> do+						fileEncoding h+						hPutStr h (unlines keylines)+						hClose h+				) 	src = PrivDataSource GpgKey "Either a gpg public key, exported with gpg --export -a, or a gpg private key, exported with gpg --export-secret-key -a"++	parse ("-----BEGIN PGP PUBLIC KEY BLOCK-----":_) = Just GpgPubKey+	parse ("-----BEGIN PGP PRIVATE KEY BLOCK-----":_) = Just GpgPrivKey+	parse _ = Nothing++	hasGpgKey Nothing = error $ "Failed to run gpg parser on armored key " ++ keyid+	hasGpgKey (Just GpgPubKey) = hasPubKey key user+	hasGpgKey (Just GpgPrivKey) = hasPrivKey key user++hasPrivKey :: GpgKeyId -> User -> IO Bool+hasPrivKey (GpgKeyId keyid) (User u) = catchBoolIO $+	snd <$> processTranscript "su" ["-c", "gpg --list-secret-keys " ++ shellEscape keyid, u] Nothing++hasPubKey :: GpgKeyId -> User -> IO Bool+hasPubKey (GpgKeyId keyid) (User u) = catchBoolIO $+	snd <$> processTranscript "su" ["-c", "gpg --list-public-keys " ++ shellEscape keyid, u] Nothing  dotDir :: User -> IO FilePath dotDir (User u) = do
src/Propellor/Property/User.hs view
@@ -18,6 +18,30 @@ 		, u 		] +systemAccountFor :: User -> Property NoInfo+systemAccountFor user@(User u) = systemAccountFor' user Nothing (Just (Group u))++systemAccountFor' :: User -> Maybe FilePath -> Maybe Group -> Property NoInfo+systemAccountFor' (User u) mhome mgroup = check nouser go+	`describe` ("system account for " ++ u)+  where+	nouser = isNothing <$> catchMaybeIO (getUserEntryForName u)+	go = cmdProperty "adduser" $+		[ "--system" ]+		+++		"--home" : maybe+			["/nonexistent", "--no-create-home"]+			( \h -> [ h ] )+			mhome+		+++		maybe [] ( \(Group g) -> ["--ingroup", g] ) mgroup+		+++		[ "--shell", "/usr/bin/nologin"+		, "--disabled-login"+		, "--disabled-password"+		, u+		]+ -- | Removes user home directory!! Use with caution. nuked :: User -> Eep -> Property NoInfo nuked user@(User u) _ = check hashomedir go@@ -131,7 +155,7 @@ 	desc = "user " ++ u ++ " is in standard desktop groups" 	-- This list comes from user-setup's debconf 	-- template named "passwd/user-default-groups"-	desktopgroups = +	desktopgroups = 		[ "audio" 		, "cdrom" 		, "dip"
src/Propellor/Spin.hs view
@@ -32,6 +32,24 @@  commitSpin :: IO () commitSpin = do+	-- safety check #1: check we're on the configured spin branch+	spinBranch <- getGitConfigValue "propellor.spin-branch"+	case spinBranch of+		Nothing -> return () -- just a noop+		Just b -> do+			currentBranch <- getCurrentBranch+			when (b /= currentBranch) $+				error ("spin aborted: check out "+ 					++ b ++ " branch first")++	-- safety check #2: check we can commit with a dirty tree+	noDirtySpin <- getGitConfigBool "propellor.forbid-dirty-spin"+	when noDirtySpin $ do+		status <- takeWhile (/= '\n')+			<$> readProcess "git" ["status", "--porcelain"]+		when (not . null $ status) $+			error "spin aborted: commit changes first"+ 	void $ actionMessage "Git commit" $ 		gitCommit (Just spinCommitMessage)  			[Param "--allow-empty", Param "-a"]
src/Propellor/Types.hs view
@@ -39,10 +39,11 @@ 	) where  import Data.Monoid-import Control.Applicative import "mtl" Control.Monad.RWS.Strict import Control.Monad.Catch import Data.Typeable+import Control.Applicative+import Prelude  import Propellor.Types.Info import Propellor.Types.OS
src/Propellor/Types/Dns.hs view
@@ -7,11 +7,12 @@ import Propellor.Types.Info  import Data.Word-import Data.Monoid import qualified Data.Map as M import qualified Data.Set as S import Data.List import Data.String.Utils (split, replace)+import Data.Monoid+import Prelude  type Domain = String 
src/Propellor/Types/Info.hs view
@@ -13,8 +13,9 @@ ) where  import Data.Dynamic-import Data.Monoid import Data.Maybe+import Data.Monoid+import Prelude  -- | Information about a Host, which can be provided by its properties. newtype Info = Info [InfoEntry]
src/Propellor/Types/Result.hs view
@@ -1,7 +1,8 @@ module Propellor.Types.Result where -import Data.Monoid import System.Console.ANSI+import Data.Monoid+import Prelude  -- | There can be three results of satisfying a Property. data Result = NoChange | MadeChange | FailedChange
src/System/Console/Concurrent/Internal.hs view
@@ -19,7 +19,6 @@ import System.Exit import Control.Monad import Control.Monad.IO.Class (liftIO, MonadIO)-import Control.Applicative import System.IO.Unsafe (unsafePerformIO) import Control.Concurrent import Control.Concurrent.STM@@ -30,6 +29,8 @@ import qualified System.Process as P import qualified Data.Text as T import qualified Data.Text.IO as T+import Control.Applicative+import Prelude  import Utility.Monad import Utility.Exception
src/Utility/Directory.hs view
@@ -13,7 +13,6 @@ import System.IO.Error import System.Directory import Control.Monad-import Control.Monad.IfElse import System.FilePath import Control.Applicative import Control.Concurrent@@ -25,10 +24,11 @@ import qualified System.Win32 as Win32 #else import qualified System.Posix as Posix+import Utility.SafeCommand+import Control.Monad.IfElse #endif  import Utility.PosixFiles-import Utility.SafeCommand import Utility.Tmp import Utility.Exception import Utility.Monad@@ -107,21 +107,32 @@ 	onrename (Left e) 		| isPermissionError e = rethrow 		| isDoesNotExistError e = rethrow-		| otherwise = do-			-- copyFile is likely not as optimised as-			-- the mv command, so we'll use the latter.-			-- But, mv will move into a directory if-			-- dest is one, which is not desired.-			whenM (isdir dest) rethrow-			viaTmp mv dest ""+		| otherwise = viaTmp mv dest "" 	  where 		rethrow = throwM e+ 		mv tmp _ = do+		-- copyFile is likely not as optimised as+		-- the mv command, so we'll use the command.+		--+		-- But, while Windows has a "mv", it does not seem very+		-- reliable, so use copyFile there.+#ifndef mingw32_HOST_OS	+			-- If dest is a directory, mv would move the file+			-- into it, which is not desired.+			whenM (isdir dest) rethrow 			ok <- boolSystem "mv" [Param "-f", Param src, Param tmp]+			let e' = e+#else+			r <- tryIO $ copyFile src tmp+			let (ok, e') = case r of+				Left err -> (False, err)+				Right _ -> (True, e)+#endif 			unless ok $ do 				-- delete any partial 				_ <- tryIO $ removeFile tmp-				rethrow+				throwM e'  	isdir f = do 		r <- tryIO $ getFileStatus f
src/Utility/Exception.hs view
@@ -20,7 +20,8 @@ 	catchNonAsync, 	tryNonAsync, 	tryWhenExists,-	catchHardwareFault,+	catchIOErrorType,+	IOErrorType(..) ) where  import Control.Monad.Catch as X hiding (Handler)@@ -88,11 +89,11 @@ 	v <- tryJust (guard . isDoesNotExistError) a 	return (eitherToMaybe v) -{- Catches only exceptions caused by hardware faults.- - Ie, disk IO error. -}-catchHardwareFault :: MonadCatch m => m a -> (IOException -> m a) -> m a-catchHardwareFault a onhardwareerr = catchIO a onlyhw+{- Catches only IO exceptions of a particular type.+ - Ie, use HardwareFault to catch disk IO errors. -}+catchIOErrorType :: MonadCatch m => IOErrorType -> (IOException -> m a) -> m a -> m a+catchIOErrorType errtype onmatchingerr a = catchIO a onlymatching   where-	onlyhw e-		| ioeGetErrorType e == HardwareFault = onhardwareerr e+	onlymatching e+		| ioeGetErrorType e == errtype = onmatchingerr e 		| otherwise = throwM e
src/Utility/FileMode.hs view
@@ -7,7 +7,10 @@  {-# LANGUAGE CPP #-} -module Utility.FileMode where+module Utility.FileMode (+	module Utility.FileMode,+	FileMode,+) where  import System.IO import Control.Monad@@ -17,18 +20,32 @@ import System.Posix.Files #endif import Foreign (complement)+import Control.Monad.IO.Class (liftIO, MonadIO)+import Control.Monad.Catch  import Utility.Exception  {- Applies a conversion function to a file's mode. -} modifyFileMode :: FilePath -> (FileMode -> FileMode) -> IO ()-modifyFileMode f convert = do+modifyFileMode f convert = void $ modifyFileMode' f convert++modifyFileMode' :: FilePath -> (FileMode -> FileMode) -> IO FileMode+modifyFileMode' f convert = do 	s <- getFileStatus f 	let old = fileMode s 	let new = convert old 	when (new /= old) $ 		setFileMode f new+	return old +{- Runs an action after changing a file's mode, then restores the old mode. -}+withModifiedFileMode :: FilePath -> (FileMode -> FileMode) -> IO a -> IO a+withModifiedFileMode file convert a = bracket setup cleanup go+  where+	setup = modifyFileMode' file convert+	cleanup oldmode = modifyFileMode file (const oldmode)+	go _ = a+ {- Adds the specified FileModes to the input mode, leaving the rest  - unchanged. -} addModes :: [FileMode] -> FileMode -> FileMode@@ -92,7 +109,7 @@  {- Runs an action without that pesky umask influencing it, unless the  - passed FileMode is the standard one. -}-noUmask :: FileMode -> IO a -> IO a+noUmask :: (MonadIO m, MonadMask m) => FileMode -> m a -> m a #ifndef mingw32_HOST_OS noUmask mode a 	| mode == stdFileMode = a@@ -101,12 +118,12 @@ noUmask _ a = a #endif -withUmask :: FileMode -> IO a -> IO a+withUmask :: (MonadIO m, MonadMask m) => FileMode -> m a -> m a #ifndef mingw32_HOST_OS withUmask umask a = bracket setup cleanup go   where-	setup = setFileCreationMask umask-	cleanup = setFileCreationMask+	setup = liftIO $ setFileCreationMask umask+	cleanup = liftIO . setFileCreationMask 	go _ = a #else withUmask _ a = a
src/Utility/FileSystemEncoding.hs view
@@ -29,6 +29,7 @@ import qualified Data.Hash.MD5 as MD5 import Data.Word import Data.Bits.Utils+import Data.List import Data.List.Utils import qualified Data.ByteString.Lazy as L #ifdef mingw32_HOST_OS@@ -125,12 +126,12 @@  {- Like encodeW8 and decodeW8, but NULs are passed through unchanged. -} encodeW8NUL :: [Word8] -> FilePath-encodeW8NUL = join nul . map encodeW8 . split (s2w8 nul)+encodeW8NUL = intercalate nul . map encodeW8 . split (s2w8 nul)   where 	nul = ['\NUL']  decodeW8NUL :: FilePath -> [Word8]-decodeW8NUL = join (s2w8 nul) . map decodeW8 . split nul+decodeW8NUL = intercalate (s2w8 nul) . map decodeW8 . split nul   where 	nul = ['\NUL'] 
src/Utility/Path.hs view
@@ -30,8 +30,8 @@ import Utility.Monad import Utility.UserInfo -{- Simplifies a path, removing any ".." or ".", and removing the trailing- - path separator.+{- Simplifies a path, removing any "." component, collapsing "dir/..", + - and removing the trailing path separator.  -  - On Windows, preserves whichever style of path separator might be used in  - the input FilePaths. This is done because some programs in Windows@@ -50,7 +50,8 @@  	norm c [] = reverse c 	norm c (p:ps)-		| p' == ".." = norm (drop 1 c) ps+		| p' == ".." && not (null c) && dropTrailingPathSeparator (c !! 0) /= ".." = +			norm (drop 1 c) ps 		| p' == "." = norm c ps 		| otherwise = norm (p:c) ps 	  where@@ -88,7 +89,7 @@ upFrom :: FilePath -> Maybe FilePath upFrom dir 	| length dirs < 2 = Nothing-	| otherwise = Just $ joinDrive drive (join s $ init dirs)+	| otherwise = Just $ joinDrive drive (intercalate s $ init dirs)   where 	-- on Unix, the drive will be "/" when the dir is absolute, otherwise "" 	(drive, path) = splitDrive dir@@ -148,7 +149,7 @@ relPathDirToFileAbs :: FilePath -> FilePath -> FilePath relPathDirToFileAbs from to 	| takeDrive from /= takeDrive to = to-	| otherwise = join s $ dotdots ++ uncommon+	| otherwise = intercalate s $ dotdots ++ uncommon   where 	s = [pathSeparator] 	pfrom = split s from@@ -287,7 +288,6 @@ 	if l <= 0 		then return 255 		else return $ minimum [l, 255]-  where #endif  {- Given a string that we'd like to use as the basis for FilePath, but that
src/Utility/SafeCommand.hs view
@@ -14,6 +14,7 @@ import Data.String.Utils import System.FilePath import Data.Char+import Data.List import Control.Applicative import Prelude @@ -85,7 +86,7 @@ shellEscape f = "'" ++ escaped ++ "'"   where 	-- replace ' with '"'"'-	escaped = join "'\"'\"'" $ split "'" f+	escaped = intercalate "'\"'\"'" $ split "'" f  -- | Unescapes a set of shellEscaped words or filenames. shellUnEscape :: String -> [String]@@ -105,10 +106,10 @@ 		| otherwise = inquote q (w++[c]) cs  -- | For quickcheck.-prop_idempotent_shellEscape :: String -> Bool-prop_idempotent_shellEscape s = [s] == (shellUnEscape . shellEscape) s-prop_idempotent_shellEscape_multiword :: [String] -> Bool-prop_idempotent_shellEscape_multiword s = s == (shellUnEscape . unwords . map shellEscape) s+prop_isomorphic_shellEscape :: String -> Bool+prop_isomorphic_shellEscape s = [s] == (shellUnEscape . shellEscape) s+prop_isomorphic_shellEscape_multiword :: [String] -> Bool+prop_isomorphic_shellEscape_multiword s = s == (shellUnEscape . unwords . map shellEscape) s  -- | Segments a list of filenames into groups that are all below the maximum --  command-line length limit.
src/Utility/Tmp.hs view
@@ -15,6 +15,9 @@ import Control.Monad.IfElse import System.FilePath import Control.Monad.IO.Class+#ifndef mingw32_HOST_OS+import System.Posix.Temp (mkdtemp)+#endif  import Utility.Exception import Utility.FileSystemEncoding@@ -64,32 +67,45 @@  - directory and all its contents. -} withTmpDir :: (MonadMask m, MonadIO m) => Template -> (FilePath -> m a) -> m a withTmpDir template a = do-	tmpdir <- liftIO $ catchDefaultIO "." getTemporaryDirectory-	withTmpDirIn tmpdir template a+	topleveltmpdir <- liftIO $ catchDefaultIO "." getTemporaryDirectory+#ifndef mingw32_HOST_OS+	-- Use mkdtemp to create a temp directory securely in /tmp.+	bracket+		(liftIO $ mkdtemp $ topleveltmpdir </> template)+		removeTmpDir+		a+#else+	withTmpDirIn topleveltmpdir template a+#endif  {- Runs an action with a tmp directory located within a specified directory,  - then removes the tmp directory and all its contents. -} withTmpDirIn :: (MonadMask m, MonadIO m) => FilePath -> Template -> (FilePath -> m a) -> m a-withTmpDirIn tmpdir template = bracketIO create remove+withTmpDirIn tmpdir template = bracketIO create removeTmpDir   where-	remove d = whenM (doesDirectoryExist d) $ do-#if mingw32_HOST_OS-		-- Windows will often refuse to delete a file-		-- after a process has just written to it and exited.-		-- Because it's crap, presumably. So, ignore failure-		-- to delete the temp directory.-		_ <- tryIO $ removeDirectoryRecursive d-		return ()-#else-		removeDirectoryRecursive d-#endif 	create = do 		createDirectoryIfMissing True tmpdir 		makenewdir (tmpdir </> template) (0 :: Int) 	makenewdir t n = do 		let dir = t ++ "." ++ show n-		either (const $ makenewdir t $ n + 1) (const $ return dir)-			=<< tryIO (createDirectory dir)+		catchIOErrorType AlreadyExists (const $ makenewdir t $ n + 1) $ do+			createDirectory dir+			return dir++{- Deletes the entire contents of the the temporary directory, if it+ - exists. -}+removeTmpDir :: MonadIO m => FilePath -> m ()+removeTmpDir tmpdir = liftIO $ whenM (doesDirectoryExist tmpdir) $ do+#if mingw32_HOST_OS+	-- Windows will often refuse to delete a file+	-- after a process has just written to it and exited.+	-- Because it's crap, presumably. So, ignore failure+	-- to delete the temp directory.+	_ <- tryIO $ removeDirectoryRecursive tmpdir+	return ()+#else+	removeDirectoryRecursive tmpdir+#endif  {- It's not safe to use a FilePath of an existing file as the template  - for openTempFile, because if the FilePath is really long, the tmpfile
src/wrapper.hs view
@@ -24,13 +24,14 @@  import Control.Monad import Control.Monad.IfElse-import Control.Applicative import System.Directory import System.FilePath import System.Environment (getArgs) import System.Exit import System.Posix.Directory import System.IO+import Control.Applicative+import Prelude  distdir :: FilePath distdir = "/usr/src/propellor"