packages feed

propellor 4.8.1 → 4.9.0

raw patch · 22 files changed

+162/−59 lines, 22 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Propellor.Types.Dns: DnsInfo :: Set Record -> DnsInfo
- Propellor.Types.Dns: [fromDnsInfo] :: DnsInfo -> Set Record
- Propellor.Types.Dns: instance GHC.Base.Monoid Propellor.Types.Dns.DnsInfo
- Propellor.Types.Dns: instance GHC.Classes.Eq Propellor.Types.Dns.DnsInfo
- Propellor.Types.Dns: instance GHC.Classes.Ord Propellor.Types.Dns.DnsInfo
- Propellor.Types.Dns: instance GHC.Show.Show Propellor.Types.Dns.DnsInfo
- Propellor.Types.Dns: instance Propellor.Types.Info.IsInfo Propellor.Types.Dns.DnsInfo
- Propellor.Types.Dns: newtype DnsInfo
- Propellor.Types.Dns: toDnsInfo :: Set Record -> DnsInfo
+ Propellor.Property.PropellorRepo: OriginUrl :: String -> OriginUrl
+ Propellor.Property.PropellorRepo: instance GHC.Show.Show Propellor.Property.PropellorRepo.OriginUrl
+ Propellor.Property.PropellorRepo: newtype OriginUrl
+ Propellor.Types.Dns: DnsInfoPropagated :: Set Record -> DnsInfoPropagated
+ Propellor.Types.Dns: DnsInfoUnpropagated :: Set Record -> DnsInfoUnpropagated
+ Propellor.Types.Dns: [fromDnsInfoPropagated] :: DnsInfoPropagated -> Set Record
+ Propellor.Types.Dns: [fromDnsInfoUnpropagated] :: DnsInfoUnpropagated -> Set Record
+ Propellor.Types.Dns: getDnsInfo :: Info -> Set Record
+ Propellor.Types.Dns: instance GHC.Base.Monoid Propellor.Types.Dns.DnsInfoPropagated
+ Propellor.Types.Dns: instance GHC.Base.Monoid Propellor.Types.Dns.DnsInfoUnpropagated
+ Propellor.Types.Dns: instance GHC.Classes.Eq Propellor.Types.Dns.DnsInfoPropagated
+ Propellor.Types.Dns: instance GHC.Classes.Eq Propellor.Types.Dns.DnsInfoUnpropagated
+ Propellor.Types.Dns: instance GHC.Classes.Ord Propellor.Types.Dns.DnsInfoPropagated
+ Propellor.Types.Dns: instance GHC.Classes.Ord Propellor.Types.Dns.DnsInfoUnpropagated
+ Propellor.Types.Dns: instance GHC.Show.Show Propellor.Types.Dns.DnsInfoPropagated
+ Propellor.Types.Dns: instance GHC.Show.Show Propellor.Types.Dns.DnsInfoUnpropagated
+ Propellor.Types.Dns: instance Propellor.Types.Info.IsInfo Propellor.Types.Dns.DnsInfoPropagated
+ Propellor.Types.Dns: instance Propellor.Types.Info.IsInfo Propellor.Types.Dns.DnsInfoUnpropagated
+ Propellor.Types.Dns: newtype DnsInfoPropagated
+ Propellor.Types.Dns: newtype DnsInfoUnpropagated
+ Propellor.Types.Dns: toDnsInfoPropagated :: Set Record -> DnsInfoPropagated
+ Propellor.Types.Dns: toDnsInfoUnpropagated :: Set Record -> DnsInfoUnpropagated
+ Propellor.Utilities: fsCruft :: FilePath -> Bool
+ Propellor.Utilities: isUnpopulated :: FilePath -> IO Bool
+ Propellor.Utilities: testDirectory :: FilePath -> (FilePath -> Bool) -> IO Bool
- Propellor.Info: addDNS :: Record -> Property (HasInfo + UnixLike)
+ Propellor.Info: addDNS :: Bool -> Record -> Property (HasInfo + UnixLike)
- Propellor.Property.PropellorRepo: hasOriginUrl :: String -> Property UnixLike
+ Propellor.Property.PropellorRepo: hasOriginUrl :: String -> Property (HasInfo + UnixLike)

Files

CHANGELOG view
@@ -1,3 +1,29 @@+propellor (4.9.0) unstable; urgency=medium++  * When the ipv4 and ipv6 properties are used with a container, avoid+    propagating the address out to the host.+  * DnsInfo has been replaced with DnsInfoPropagated and+    DnsInfoUnpropagated. (API change)+  * Code that used fromDnsInfo . fromInfo changes to use getDnsInfo.+  * addDNS takes an additional Bool parameter to control whether+    the DNS info should propagate out of containers. (API change)+  * Made the PropellorRepo.hasOriginUrl property override the repository+    url that --spin passes to a host.+  * PropellorRepo.hasOriginUrl type changed to include HasInfo. (API change)+  * Fstab.mounted: Create mount point if necessary, and mount it+    if it's not already mounted.+    Thanks, Nicolas Schodet+  * Properties that check for an empty directory now treat a directory+    containing only "lost+found" as effectively empty, to support+    situations where the directory is a mount point of an EXT* filesystem.+    Thanks, Nicolas Schodet+  * Make addInfo accumulate Info in order properties appear, not+    reverse order.+    This fixes a bug involving reverting Systemd.resolvConfed or+    Systemd.linkJournal.++ -- Joey Hess <id@joeyh.name>  Wed, 25 Oct 2017 13:02:14 -0400+ propellor (4.8.1) unstable; urgency=medium    * Borg: Fix propigation of exit status of borg backup.
debian/changelog view
@@ -1,3 +1,29 @@+propellor (4.9.0) unstable; urgency=medium++  * When the ipv4 and ipv6 properties are used with a container, avoid+    propagating the address out to the host.+  * DnsInfo has been replaced with DnsInfoPropagated and+    DnsInfoUnpropagated. (API change)+  * Code that used fromDnsInfo . fromInfo changes to use getDnsInfo.+  * addDNS takes an additional Bool parameter to control whether+    the DNS info should propagate out of containers. (API change)+  * Made the PropellorRepo.hasOriginUrl property override the repository+    url that --spin passes to a host.+  * PropellorRepo.hasOriginUrl type changed to include HasInfo. (API change)+  * Fstab.mounted: Create mount point if necessary, and mount it+    if it's not already mounted.+    Thanks, Nicolas Schodet+  * Properties that check for an empty directory now treat a directory+    containing only "lost+found" as effectively empty, to support+    situations where the directory is a mount point of an EXT* filesystem.+    Thanks, Nicolas Schodet+  * Make addInfo accumulate Info in order properties appear, not+    reverse order.+    This fixes a bug involving reverting Systemd.resolvConfed or+    Systemd.linkJournal.++ -- Joey Hess <id@joeyh.name>  Wed, 25 Oct 2017 13:02:14 -0400+ propellor (4.8.1) unstable; urgency=medium    * Borg: Fix propigation of exit status of borg backup.
debian/control view
@@ -19,7 +19,6 @@ 	libghc-stm-dev, 	libghc-text-dev, 	libghc-hashable-dev,-	libghc-concurrent-output-dev, Maintainer: Joey Hess <id@joeyh.name> Standards-Version: 3.9.8 Vcs-Git: git://git.joeyh.name/propellor@@ -44,7 +43,6 @@ 	libghc-stm-dev, 	libghc-text-dev, 	libghc-hashable-dev,-	libghc-concurrent-output-dev, 	git, Description: property-based host configuration management in haskell  Propellor ensures that the system it's run in satisfies a list of
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 4.8.1+Version: 4.9.0 Cabal-Version: >= 1.20 License: BSD2 Maintainer: Joey Hess <id@joeyh.name>
src/Propellor/Bootstrap.hs view
@@ -133,9 +133,9 @@ 	pkginstall p = "ASSUME_ALWAYS_YES=yes pkg install " ++ p 	pacmaninstall p = "pacman -S --noconfirm --needed " ++ p -	-- This is the same deps listed in debian/control. 	debdeps Cabal = 		[ "gnupg"+		-- Below are the same deps listed in debian/control. 		, "ghc" 		, "cabal-install" 		, "libghc-async-dev"
src/Propellor/Info.hs view
@@ -128,11 +128,11 @@ -- if the host's IP Property matches the DNS. If the DNS is missing or -- out of date, the host will instead be contacted directly by IP address. ipv4 :: String -> Property (HasInfo + UnixLike)-ipv4 = addDNS . Address . IPv4+ipv4 = addDNS False . Address . IPv4  -- | Indicate that a host has an AAAA record in the DNS. ipv6 :: String -> Property (HasInfo + UnixLike)-ipv6 = addDNS . Address . IPv6+ipv6 = addDNS False . Address . IPv6  -- | Indicates another name for the host in the DNS. --@@ -145,19 +145,29 @@ 	`addInfo` toAliasesInfo [d] 	-- A CNAME is added here, but the DNS setup code converts it to an 	-- IP address when that makes sense.-	`addInfo` (toDnsInfo $ S.singleton $ CNAME $ AbsDomain d)+	`addInfo` (toDnsInfoPropagated $ S.singleton $ CNAME $ AbsDomain d) -addDNS :: Record -> Property (HasInfo + UnixLike)-addDNS r = pureInfoProperty (rdesc r) (toDnsInfo (S.singleton r))+-- | Add a DNS Record.+addDNS+	:: Bool+	-- ^ When used in a container, the DNS info will only+	-- propagate out the the Host when this is True.+	-> Record+	-> Property (HasInfo + UnixLike)+addDNS prop r+	| prop = pureInfoProperty (rdesc r) (toDnsInfoPropagated s)+	| otherwise = pureInfoProperty (rdesc r) (toDnsInfoUnpropagated s)   where+	s = S.singleton r+ 	rdesc (CNAME d) = unwords ["alias", ddesc d] 	rdesc (Address (IPv4 addr)) = unwords ["ipv4", addr] 	rdesc (Address (IPv6 addr)) = unwords ["ipv6", addr] 	rdesc (MX n d) = unwords ["MX", show n, ddesc d] 	rdesc (NS d) = unwords ["NS", ddesc d]-	rdesc (TXT s) = unwords ["TXT", s]+	rdesc (TXT t) = unwords ["TXT", t] 	rdesc (SRV x y z d) = unwords ["SRV", show x, show y, show z, ddesc d]-	rdesc (SSHFP x y s) = unwords ["SSHFP", show x, show y, s]+	rdesc (SSHFP x y t) = unwords ["SSHFP", show x, show y, t] 	rdesc (INCLUDE f) = unwords ["$INCLUDE", f] 	rdesc (PTR x) = unwords ["PTR", x] @@ -182,7 +192,7 @@ findAlias l hn = M.lookup hn (aliasMap l)  getAddresses :: Info -> [IPAddr]-getAddresses = mapMaybe getIPAddr . S.toList . fromDnsInfo . fromInfo+getAddresses = mapMaybe getIPAddr . S.toList . getDnsInfo  hostAddresses :: HostName -> [Host] -> [IPAddr] hostAddresses hn hosts = maybe [] (getAddresses . hostInfo) (findHost hosts hn)
src/Propellor/Property/Attic.hs view
@@ -59,7 +59,7 @@ 		, noChange 		) -	needsRestore = null <$> catchDefaultIO [] (dirContents dir)+	needsRestore = isUnpopulated dir  	restore = withTmpDirIn (takeDirectory dir) "attic-restore" $ \tmpdir -> do 		ok <- boolSystem "attic" $
src/Propellor/Property/Borg.hs view
@@ -95,7 +95,7 @@ 		, noChange 		) -	needsRestore = null <$> catchDefaultIO [] (dirContents dir)+	needsRestore = isUnpopulated dir  	restore = withTmpDirIn (takeDirectory dir) "borg-restore" $ \tmpdir -> do 		ok <- runBorg repo $
src/Propellor/Property/Chroot.hs view
@@ -77,7 +77,7 @@ 		tightenTargets $ extractTarball loc tb  extractTarball :: FilePath -> FilePath -> Property UnixLike-extractTarball target src = check (unpopulated target) $+extractTarball target src = check (isUnpopulated target) $ 	cmdProperty "tar" params 		`assume` MadeChange 		`requires` File.dirExists target@@ -151,7 +151,7 @@ 	cantbuild e = property (chrootDesc c "built") (error e)  	teardown :: Property Linux-	teardown = check (not <$> unpopulated loc) $+	teardown = check (not <$> isUnpopulated loc) $ 		property ("removed " ++ loc) $ 			makeChange (removeChroot loc) 
src/Propellor/Property/Chroot/Util.hs view
@@ -2,7 +2,6 @@  import Propellor.Property.Mount -import Utility.Exception import Utility.Env import Utility.Directory @@ -27,7 +26,3 @@ removeChroot c = do 	unmountBelow c 	removeDirectoryRecursive c---- | Returns true if a chroot directory is empty.-unpopulated :: FilePath -> IO Bool-unpopulated d = null <$> catchDefaultIO [] (dirContents d)
src/Propellor/Property/Debootstrap.hs view
@@ -54,7 +54,7 @@ built' installprop target system@(System _ arch) config =  	go `before` oldpermfix   where-	go = check (unpopulated target <||> ispartial) setupprop+	go = check (isUnpopulated target <||> ispartial) setupprop 		`requires` installprop  	setupprop :: Property Linux
src/Propellor/Property/Dns.hs view
@@ -468,7 +468,7 @@ 	-- So we can just use the IPAddrs. 	addcnames :: Host -> [Either WarningMessage (BindDomain, Record)] 	addcnames h = concatMap gen $ filter (inDomain zdomain) $-		mapMaybe getCNAME $ S.toList $ fromDnsInfo $ fromInfo info+		mapMaybe getCNAME $ S.toList $ getDnsInfo info 	  where 		info = hostInfo h 		gen c = case getAddresses info of@@ -483,7 +483,7 @@ 	  where 		info = hostInfo h 		l = zip (repeat $ AbsDomain $ hostName h)-			(S.toList $ S.filter (\r -> isNothing (getIPAddr r) && isNothing (getCNAME r)) (fromDnsInfo $ fromInfo info))+			(S.toList $ S.filter (\r -> isNothing (getIPAddr r) && isNothing (getCNAME r)) (getDnsInfo info))  	-- Simplifies the list of hosts. Remove duplicate entries. 	-- Also, filter out any CHAMES where the same domain has an@@ -531,7 +531,7 @@ 	gen = liftIO . mapM genSSHFP' . M.elems . fromMaybe M.empty 	mk r = mapMaybe (\d -> if inDomain domain d then Just (d, r) else Nothing) 		(AbsDomain hostname : cnames)-	cnames = mapMaybe getCNAME $ S.toList $ fromDnsInfo $ fromInfo info+	cnames = mapMaybe getCNAME $ S.toList $ getDnsInfo info 	hostname = hostName h 	info = hostInfo h 
src/Propellor/Property/Fstab.hs view
@@ -26,7 +26,8 @@ mounted :: FsType -> Source -> MountPoint -> MountOpts -> Property Linux mounted fs src mnt opts = tightenTargets $  	listed fs src mnt opts-		`onChange` mountnow+		`before` mountnow+		`requires` File.dirExists mnt   where 	-- This use of mountPoints, which is linux-only, is why this 	-- property currently only supports linux.
src/Propellor/Property/Obnam.hs view
@@ -113,7 +113,7 @@ 		, noChange 		) -	needsRestore = null <$> catchDefaultIO [] (dirContents dir)+	needsRestore = isUnpopulated dir  	restore = withTmpDirIn (takeDirectory dir) "obnam-restore" $ \tmpdir -> do 		ok <- boolSystem "obnam" $
src/Propellor/Property/PropellorRepo.hs view
@@ -2,18 +2,26 @@  import Propellor.Base import Propellor.Git.Config+import Propellor.Types.Info  -- | Sets the url to use as the origin of propellor's git repository. ----- When propellor --spin is used to update a host, the url is taken from--- the repository that --spin is run in, and passed to the host. So, you--- don't need to specifiy this property then. +-- By default, the url is taken from the deploy or origin remote of+-- the repository that propellor --spin is run in. Setting this property+-- overrides that default behavior with a different url. ----- This property is useful when hosts are being updated without using--- --spin, eg when using the `Propellor.Property.Cron.runPropellor` cron job.-hasOriginUrl :: String -> Property UnixLike-hasOriginUrl u = property ("propellor repo url " ++ u) $ do-	curru <- liftIO getRepoUrl-	if curru == Just u-		then return NoChange-		else makeChange $ setRepoUrl u+-- When hosts are being updated without using -- --spin, eg when using+-- the `Propellor.Property.Cron.runPropellor` cron job, this property can+-- be set to redirect them to a new git repository url.+hasOriginUrl :: String -> Property (HasInfo + UnixLike)+hasOriginUrl u = setInfoProperty p (toInfo (InfoVal (OriginUrl u)))+  where+	p :: Property UnixLike+	p = property ("propellor repo url " ++ u) $ do+		curru <- liftIO getRepoUrl+		if curru == Just u+			then return NoChange+			else makeChange $ setRepoUrl u++newtype OriginUrl = OriginUrl String+	deriving (Show)
src/Propellor/Property/Restic.hs view
@@ -97,7 +97,7 @@ 		, noChange 		) -	needsRestore = null <$> catchDefaultIO [] (dirContents dir)+	needsRestore = isUnpopulated dir  	restore = withTmpDirIn (takeDirectory dir) "restic-restore" $ \tmpdir -> do 		ok <- boolSystem "restic"
src/Propellor/Property/Sbuild.hs view
@@ -147,7 +147,7 @@ 	<!> deleted   where 	go :: Property DebianLike-	go = check (unpopulated (schrootRoot s) <||> ispartial) $+	go = check (isUnpopulated (schrootRoot s) <||> ispartial) $ 		property' ("built sbuild schroot for " ++ val s) make 	make w = do 		de <- liftIO standardPathEnv@@ -166,7 +166,7 @@ 			) 	-- TODO we should kill any sessions still using the chroot 	-- before destroying it (as suggested by sbuild-destroychroot)-	deleted = check (not <$> unpopulated (schrootRoot s)) $+	deleted = check (not <$> isUnpopulated (schrootRoot s)) $ 		property ("no sbuild schroot for " ++ val s) $ do 			liftIO $ removeChroot $ schrootRoot s 			liftIO $ nukeFile
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -912,7 +912,7 @@ homePowerMonitor :: IsContext c => User -> c -> (SshKeyType, Ssh.PubKeyText) -> Property (HasInfo + DebianLike) homePowerMonitor user ctx sshkey = propertyList "home power monitor" $ props 	& Apache.installed-	& Apt.installed ["python2", "python-pymodbus"]+	& Apt.installed ["python", "python-pymodbus"] 	& File.ownerGroup "/var/www/html" user (userGroup user) 	& Git.cloned user "git://git.kitenet.net/joey/homepower" d Nothing 		`onChange` buildpoller
src/Propellor/Spin.hs view
@@ -29,6 +29,7 @@ import Propellor.Bootstrap import Propellor.Types.CmdLine import Propellor.Types.Info+import Propellor.Property.PropellorRepo (OriginUrl(..)) import qualified Propellor.Shim as Shim import Utility.FileMode import Utility.SafeCommand@@ -173,7 +174,7 @@ 					return ip  	configips = map val $ mapMaybe getIPAddr $-		S.toList $ fromDnsInfo $ fromInfo $ hostInfo hst+		S.toList $ getDnsInfo $ hostInfo hst  -- Update the privdata, repo url, and git repo over the ssh -- connection, talking to the user's local propellor instance which is@@ -220,7 +221,7 @@ 		v <- maybe Nothing readish <$> getMarked fromh statusMarker 		case v of 			(Just NeedRepoUrl) -> do-				sendRepoUrl toh+				sendRepoUrl hst toh 				loop 			(Just NeedPrivData) -> do 				sendPrivData hn toh privdata@@ -242,8 +243,12 @@ 				done 			Nothing -> done -sendRepoUrl :: Handle -> IO ()-sendRepoUrl toh = sendMarked toh repoUrlMarker =<< (fromMaybe "" <$> getRepoUrl)+sendRepoUrl :: Host -> Handle -> IO ()+sendRepoUrl hst toh = sendMarked toh repoUrlMarker =<< geturl+  where+	geturl = case fromInfoVal (fromInfo (hostInfo hst)) of+		Nothing -> fromMaybe "" <$> getRepoUrl+		Just (OriginUrl u) -> return u  sendPrivData :: HostName -> Handle -> PrivMap -> IO () sendPrivData hn toh privdata = void $ actionMessage msg $ do
src/Propellor/Types/Dns.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-}  module Propellor.Types.Dns where @@ -36,16 +37,36 @@ fromAliasesInfo :: AliasesInfo -> [HostName] fromAliasesInfo (AliasesInfo s) = S.toList s -newtype DnsInfo = DnsInfo { fromDnsInfo :: S.Set Record }+-- | Use this for DNS Info that should propagate from a container to a+-- host. For example, this can be used for CNAME to make aliases+-- of the containers in the host be reflected in the DNS.+newtype DnsInfoPropagated = DnsInfoPropagated+	{ fromDnsInfoPropagated :: S.Set Record } 	deriving (Show, Eq, Ord, Monoid, Typeable) -toDnsInfo :: S.Set Record -> DnsInfo-toDnsInfo = DnsInfo+toDnsInfoPropagated :: S.Set Record -> DnsInfoPropagated+toDnsInfoPropagated = DnsInfoPropagated --- | DNS Info is propagated, so that eg, aliases of a container--- are reflected in the dns for the host where it runs.-instance IsInfo DnsInfo where+instance IsInfo DnsInfoPropagated where 	propagateInfo _ = PropagateInfo True++-- | Use this for DNS Info that should not propagate from a container to a+-- host. For example, an IP address of a container should not influence+-- the host.+newtype DnsInfoUnpropagated = DnsInfoUnpropagated+	{ fromDnsInfoUnpropagated :: S.Set Record }+	deriving (Show, Eq, Ord, Monoid, Typeable)++toDnsInfoUnpropagated :: S.Set Record -> DnsInfoUnpropagated+toDnsInfoUnpropagated = DnsInfoUnpropagated++-- | Get all DNS Info.+getDnsInfo :: Info -> S.Set Record+getDnsInfo i = fromDnsInfoUnpropagated (fromInfo i)+	`S.union` fromDnsInfoPropagated (fromInfo i)++instance IsInfo DnsInfoUnpropagated where+	propagateInfo _ = PropagateInfo False  -- | Represents a bind 9 named.conf file. data NamedConf = NamedConf
src/Propellor/Types/Info.hs view
@@ -44,8 +44,7 @@ -- as info, especially type aliases which coud easily lead to bugs. -- We want a little bit of dynamic types here, but not too far.. class (Typeable v, Monoid v, Show v) => IsInfo v where-	-- | Should info of this type be propagated out of a-	-- container to its Host?+	-- | Should this info be propagated out of a container to its Host? 	propagateInfo :: v -> PropagateInfo  data PropagateInfo@@ -56,16 +55,15 @@  -- | Any value in the `IsInfo` type class can be added to an Info. addInfo :: IsInfo v => Info -> v -> Info-addInfo (Info l) v = Info (InfoEntry v:l)+addInfo (Info l) v = Info (l++[InfoEntry v])  -- | Converts any value in the `IsInfo` type class into an Info, -- which is otherwise empty. toInfo :: IsInfo v => v -> Info toInfo = addInfo mempty --- The list is reversed here because addInfo builds it up in reverse order. fromInfo :: IsInfo v => Info -> v-fromInfo (Info l) = mconcat (mapMaybe extractInfoEntry (reverse l))+fromInfo (Info l) = mconcat (mapMaybe extractInfoEntry l)  -- | Maps a function over all values stored in the Info that are of the -- appropriate type.
src/Utility/Directory.hs view
@@ -42,6 +42,10 @@ dirCruft ".." = True dirCruft _ = False +fsCruft :: FilePath -> Bool+fsCruft "lost+found" = True+fsCruft d = dirCruft d+ {- Lists the contents of a directory.  - Unlike getDirectoryContents, paths are not relative to the directory. -} dirContents :: FilePath -> IO [FilePath]@@ -236,12 +240,23 @@ -- True only when directory exists and contains nothing. -- Throws exception if directory does not exist. isDirectoryEmpty :: FilePath -> IO Bool-isDirectoryEmpty d = bracket (openDirectory d) closeDirectory check+isDirectoryEmpty d = testDirectory d dirCruft++-- | True if the directory does not exist or contains nothing.+-- Ignores "lost+found" which can exist in an empty filesystem.+isUnpopulated :: FilePath -> IO Bool+isUnpopulated d = catchDefaultIO True $ testDirectory d fsCruft++-- | Run test on entries found in directory, return False as soon as the+-- test returns False, else return True.  Throws exception if directory does+-- not exist.+testDirectory :: FilePath -> (FilePath -> Bool) -> IO Bool+testDirectory d test = bracket (openDirectory d) closeDirectory check   where 	check h = do 		v <- readDirectory h 		case v of 			Nothing -> return True 			Just f-				| not (dirCruft f) -> return False+				| not (test f) -> return False 				| otherwise -> check h