diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,36 @@
+## v0.2.6
+
+- Supporting GHC 9.2 or later only.
+- Fixing `setFileSize` leaking a file handle on Windows.
+- `hasSubDirectories` returns `Nothing` on Windows instead of
+  throwing an exception.
+- Documenting that timestamps are truncated to seconds and that
+  `canonicalizePath` may return a path with `\` on Windows.
+
+## v0.2.5
+
+- setFileSizer can be built but raises an error for GHC < 8.10.6.
+  [#10](https://github.com/kazu-yamamoto/easy-file/pull/10)
+
+## v0.2.4
+
+- Providing `setFileSize`.
+
+## v0.2.3
+
+- Catching up the current environment (2018 -> 2023)
+  [#7](https://github.com/kazu-yamamoto/easy-file/pull/7)
+
+## v0.2.2
+
+- win32 file open for getting metadata
+  [#5](https://github.com/kazu-yamamoto/easy-file/pull/5)
+
+## v0.2.1
+
+- Fixing the document
+  [#3](https://github.com/kazu-yamamoto/easy-file/pull/3)
+
+## v0.2.0
+
+- Providing `getFileSize`.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+![GitHub Actions status](https://github.com/kazu-yamamoto/easy-file/workflows/Haskell%20CI/badge.svg)
+
+# Easy file handling for Haskell
+
+This is a Haskell package of cross-platform file handling for Unix/Mac/Windows.
+
+The current Haskell modules have following shortcomings:
+
+- getModificationTime exists in System.Directory. But getAccessTime,
+  getStatusChangeTime, getCreationTime do not exist.
+
+- getModificationTime returns obsoleted type, ClockTime. It should
+  return modern type, UTCTime, I believe.
+
+- Some file functions are missing. A function to tell the link counter,
+  for instance.
+
+- Path separator is not unified. Even though Windows accepts '/' as a
+  file separator, getCurrentDirectory in System.Directory returns '\\'
+  as a file separator. So, we need to specify regular expression like
+  this: "[/\\]foo[/\\]bar[/\\]baz".
+
+- getHomeDirectory returns HOMEDRIVE/HOMEPATH instead of the HOME
+  environment variable on Windows.
+
+This package aims to resolve these problems.
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,3 @@
 import Distribution.Simple
+
 main = defaultMain
diff --git a/System/EasyFile.hs b/System/EasyFile.hs
--- a/System/EasyFile.hs
+++ b/System/EasyFile.hs
@@ -1,88 +1,93 @@
-{-|
-This is a module of cross-platform file handling for Unix\/Mac\/Windows.
-
-The standard module "System.Directory" and "System.FilePath" have
-following shortcomings:
-
-* getModificationTime exists in "System.Directory". But getAccessTime,
-  getChangeTime, getCreationTime do not exist.
-
-* getModificationTime returns obsoleted type, 'ClockTime'. It should
-  return modern type, 'UTCTime', I believe.
-
-* Some file functions are missing. A function to tell the link counter,
-  for instance.
-
-* Path separator is not unified. Even though Windows accepts \'\/\' as a
-  file separator, getCurrentDirectory in "System.Directory" returns \'\\\'
-  as a file separator. So, we need to specify regular expression like
-  this: \"[\/\\\\]foo[\/\\\\]bar[\/\\\\]baz\".
-
-* getHomeDirectory returns @HOMEDRIVE@\/@HOMEPATH@ instead of the @HOME@
-  environment variable on Windows.
-
-This module aims to resolve these problems and provides:
+-- |
+-- This is a module of cross-platform file handling for Unix\/Mac\/Windows.
+--
+-- The standard module "System.Directory" and "System.FilePath" have
+-- following shortcomings:
+--
+-- * getModificationTime exists in "System.Directory". But getAccessTime,
+--   getChangeTime, getCreationTime do not exist.
+--
+-- * getModificationTime returns obsoleted type, 'ClockTime'. It should
+--   return modern type, 'UTCTime', I believe.
+--
+-- * Some file functions are missing. A function to tell the link counter,
+--   for instance.
+--
+-- * Path separator is not unified. Even though Windows accepts \'\/\' as a
+--   file separator, getCurrentDirectory in "System.Directory" returns \'\\\'
+--   as a file separator. So, we need to specify regular expression like
+--   this: \"[\/\\\\]foo[\/\\\\]bar[\/\\\\]baz\".
+--
+-- * getHomeDirectory returns @HOMEDRIVE@\/@HOMEPATH@ instead of the @HOME@
+--   environment variable on Windows.
+--
+-- This module aims to resolve these problems and provides:
+--
+-- * 'getModificationTime', 'getAccessTime', 'getChangeTime', and
+--   'getCreationTime'. They return 'UTCTime', truncated to seconds.
+--
+-- * 'isSymlink', 'getLinkCount', and 'hasSubDirectories'.
+--
+-- * \'\/\' as the single 'pathSeparator'. For instance,
+--   'getCurrentDirectory' returns a path whose separator is \'\/\'
+--   even on Windows. Note that 'canonicalizePath', re-exported from
+--   "System.Directory" as is, may return a path with \'\\\' on Windows.
+--
+-- * 'getHomeDirectory2' which refers the @HOME@ environment variable.
+--
+-- * Necessary functions in "System.Directory" and "System.FilePath".
+module System.EasyFile (
+    -- * Actions on directories
+    createDirectory,
+    createDirectoryIfMissing,
+    removeDirectory,
+    removeDirectoryRecursive,
+    renameDirectory,
+    getDirectoryContents,
+    getCurrentDirectory,
+    setCurrentDirectory,
 
-* 'getModificationTime', 'getAccessTime', 'getChangeTime', and
-  'getCreationTime'. They return 'UTCTime'.
+    -- * Pre-defined directories
+    getHomeDirectory,
+    getHomeDirectory2, -- missing
+    getAppUserDataDirectory,
+    getUserDocumentsDirectory,
+    getTemporaryDirectory,
 
-* 'isSymlink', 'getLinkCount', and 'hasSubDirectories'.
+    -- * Actions on files
+    removeFile,
+    renameFile,
+    copyFile,
+    canonicalizePath,
+    --   , makeRelativeToCurrentDirectory -- xxx
+    --   , findExecutable -- xxx
 
-* \'\/\' as the single 'pathSeparator'. For instance,
-  'getCurrentDirectory' returns a path whose separator is \'\/\'
-  even on Windows.
+    -- * Existence tests
+    doesFileExist,
+    doesDirectoryExist,
 
-* 'getHomeDirectory2' which refers the @HOME@ environment variable.
+    -- * Permissions
+    Permissions (..),
+    getPermissions,
+    setPermissions,
+    copyPermissions,
 
-* Necessary functions in "System.Directory" and "System.FilePath".
+    -- * Timestamps
+    getCreationTime,
+    getChangeTime,
+    getModificationTime,
+    getAccessTime,
 
--}
+    -- * Size
+    getFileSize,
+    setFileSize,
 
-module System.EasyFile (
-  -- * Actions on directories
-    createDirectory
-  , createDirectoryIfMissing
-  , removeDirectory
-  , removeDirectoryRecursive
-  , renameDirectory
-  , getDirectoryContents
-  , getCurrentDirectory
-  , setCurrentDirectory
-  -- * Pre-defined directories
-  , getHomeDirectory
-  , getHomeDirectory2 -- missing
-  , getAppUserDataDirectory
-  , getUserDocumentsDirectory
-  , getTemporaryDirectory
-  -- * Actions on files
-  , removeFile
-  , renameFile
-  , copyFile
-  , canonicalizePath
---   , makeRelativeToCurrentDirectory -- xxx
---   , findExecutable -- xxx
-  -- * Existence tests
-  , doesFileExist
-  , doesDirectoryExist
-  -- * Permissions
-  , Permissions(..)
-  , getPermissions
-  , setPermissions
-  , copyPermissions
-  -- * Timestamps
-  , getCreationTime
-  , getChangeTime
-  , getModificationTime
-  , getAccessTime
-  -- * Size
-  , getFileSize
-  , setFileSize
-  -- * File\/directory information
-  , isSymlink
-  , getLinkCount
-  , hasSubDirectories
-  , module System.EasyFile.FilePath
-  ) where
+    -- * File\/directory information
+    isSymlink,
+    getLinkCount,
+    hasSubDirectories,
+    module System.EasyFile.FilePath,
+) where
 
 ----------------------------------------------------------------
 
diff --git a/System/EasyFile/Directory.hs b/System/EasyFile/Directory.hs
--- a/System/EasyFile/Directory.hs
+++ b/System/EasyFile/Directory.hs
@@ -1,185 +1,172 @@
 {-# LANGUAGE CPP #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 
 module System.EasyFile.Directory (
-    module System.EasyFile.Directory
-  , module System.Directory
-  ) where
+    module System.EasyFile.Directory,
+    module System.Directory,
+) where
 
 import System.Directory (
-    createDirectory
-  , createDirectoryIfMissing
-  , removeDirectory
-  , removeDirectoryRecursive
-  , renameDirectory
-  , getDirectoryContents
-  , setCurrentDirectory
-  , removeFile
-  , renameFile
-  , copyFile
-  , canonicalizePath
-  , doesFileExist
-  , doesDirectoryExist
-  , Permissions(..)
-  , getPermissions
-  , setPermissions
-  )
+    Permissions (..),
+    canonicalizePath,
+    copyFile,
+    createDirectory,
+    createDirectoryIfMissing,
+    doesDirectoryExist,
+    doesFileExist,
+    getDirectoryContents,
+    getPermissions,
+    removeDirectory,
+    removeDirectoryRecursive,
+    removeFile,
+    renameDirectory,
+    renameFile,
+    setCurrentDirectory,
+    setPermissions,
+ )
 
 import qualified System.Directory as D (
-    getCurrentDirectory
-  , getHomeDirectory
-  , getAppUserDataDirectory
-  , getUserDocumentsDirectory
-  , getTemporaryDirectory
-  , copyPermissions
-  )
+    copyPermissions,
+    getAppUserDataDirectory,
+    getCurrentDirectory,
+    getHomeDirectory,
+    getTemporaryDirectory,
+    getUserDocumentsDirectory,
+ )
 
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative
-#endif
-import qualified Control.Exception as E
 import System.Environment
+import qualified System.IO.Error as E
 
 ----------------------------------------------------------------
 
-{- |If the operating system has a notion of current directories,
-'getCurrentDirectory' returns an absolute path to the
-current directory of the calling process.
-
-The operation may fail with:
-
-* 'HardwareFault'
-A physical I\/O error has occurred.
-@[EIO]@
-
-* 'isDoesNotExistError' \/ 'NoSuchThing'
-There is no path referring to the current directory.
-@[EPERM, ENOENT, ESTALE...]@
-
-* 'isPermissionError' \/ 'PermissionDenied'
-The process has insufficient privileges to perform the operation.
-@[EACCES]@
-
-* 'ResourceExhausted'
-Insufficient resources are available to perform the operation.
-
-* 'UnsupportedOperation'
-The operating system has no notion of current directory.
-
--}
+-- | If the operating system has a notion of current directories,
+-- 'getCurrentDirectory' returns an absolute path to the
+-- current directory of the calling process.
+--
+-- The operation may fail with:
+--
+-- * 'HardwareFault'
+-- A physical I\/O error has occurred.
+-- @[EIO]@
+--
+-- * 'isDoesNotExistError' \/ 'NoSuchThing'
+-- There is no path referring to the current directory.
+-- @[EPERM, ENOENT, ESTALE...]@
+--
+-- * 'isPermissionError' \/ 'PermissionDenied'
+-- The process has insufficient privileges to perform the operation.
+-- @[EACCES]@
+--
+-- * 'ResourceExhausted'
+-- Insufficient resources are available to perform the operation.
+--
+-- * 'UnsupportedOperation'
+-- The operating system has no notion of current directory.
 getCurrentDirectory :: IO FilePath
 getCurrentDirectory = fixPath <$> D.getCurrentDirectory
 
-{- | Returns the current user's home directory.
-
-The directory returned is expected to be writable by the current user,
-but note that it isn't generally considered good practice to store
-application-specific data here; use 'getAppUserDataDirectory'
-instead.
-
-On Unix, 'getHomeDirectory' returns the value of the @HOME@
-environment variable.  On Windows, the system is queried for a
-suitable path; a typical path might be
-@C:/Documents And Settings/user@.
-
-The operation may fail with:
-
-* 'UnsupportedOperation'
-The operating system has no notion of home directory.
-
-* 'isDoesNotExistError'
-The home directory for the current user does not exist, or
-cannot be found.
--}
+-- | Returns the current user's home directory.
+--
+-- The directory returned is expected to be writable by the current user,
+-- but note that it isn't generally considered good practice to store
+-- application-specific data here; use 'getAppUserDataDirectory'
+-- instead.
+--
+-- On Unix, 'getHomeDirectory' returns the value of the @HOME@
+-- environment variable.  On Windows, the system is queried for a
+-- suitable path; a typical path might be
+-- @C:/Documents And Settings/user@.
+--
+-- The operation may fail with:
+--
+-- * 'UnsupportedOperation'
+-- The operating system has no notion of home directory.
+--
+-- * 'isDoesNotExistError'
+-- The home directory for the current user does not exist, or
+-- cannot be found.
 getHomeDirectory :: IO FilePath
 getHomeDirectory = fixPath <$> D.getHomeDirectory
 
-{- | Returns the current user's home directory from
-the @HOME@ environment variable.
--}
-
+-- | Returns the current user's home directory from
+-- the @HOME@ environment variable.
 getHomeDirectory2 :: IO (Maybe FilePath)
-getHomeDirectory2 = (Just . fixPath <$> getEnv "HOME") `E.catch`
-                    \(_ :: E.IOException) -> return Nothing
-
-{- | Returns the pathname of a directory in which application-specific
-data for the current user can be stored.  The result of
-'getAppUserDataDirectory' for a given application is specific to
-the current user.
-
-The argument should be the name of the application, which will be used
-to construct the pathname (so avoid using unusual characters that
-might result in an invalid pathname).
-
-Note: the directory may not actually exist, and may need to be created
-first.  It is expected that the parent directory exists and is
-writable.
-
-On Unix, this function returns @$HOME\/.appName@.  On Windows, a
-typical path might be
-
-> C:/Documents And Settings/user/Application Data/appName
-
-The operation may fail with:
-
-* 'UnsupportedOperation'
-The operating system has no notion of application-specific data directory.
-
-* 'isDoesNotExistError'
-The home directory for the current user does not exist, or
-cannot be found.
--}
+getHomeDirectory2 =
+    (Just . fixPath <$> getEnv "HOME")
+        `E.catchIOError` \_ -> return Nothing
 
+-- | Returns the pathname of a directory in which application-specific
+-- data for the current user can be stored.  The result of
+-- 'getAppUserDataDirectory' for a given application is specific to
+-- the current user.
+--
+-- The argument should be the name of the application, which will be used
+-- to construct the pathname (so avoid using unusual characters that
+-- might result in an invalid pathname).
+--
+-- Note: the directory may not actually exist, and may need to be created
+-- first.  It is expected that the parent directory exists and is
+-- writable.
+--
+-- On Unix, this function returns @$HOME\/.appName@.  On Windows, a
+-- typical path might be
+--
+-- > C:/Documents And Settings/user/Application Data/appName
+--
+-- The operation may fail with:
+--
+-- * 'UnsupportedOperation'
+-- The operating system has no notion of application-specific data directory.
+--
+-- * 'isDoesNotExistError'
+-- The home directory for the current user does not exist, or
+-- cannot be found.
 getAppUserDataDirectory :: String -> IO FilePath
 getAppUserDataDirectory x = fixPath <$> D.getAppUserDataDirectory x
 
-{- | Returns the current user's document directory.
-
-The directory returned is expected to be writable by the current user,
-but note that it isn't generally considered good practice to store
-application-specific data here; use 'getAppUserDataDirectory'
-instead.
-
-On Unix, 'getUserDocumentsDirectory' returns the value of the @HOME@
-environment variable.  On Windows, the system is queried for a
-suitable path; a typical path might be
-@C:\/Documents and Settings\/user\/My Documents@.
-
-The operation may fail with:
-
-* 'UnsupportedOperation'
-The operating system has no notion of document directory.
-
-* 'isDoesNotExistError'
-The document directory for the current user does not exist, or
-cannot be found.
--}
+-- | Returns the current user's document directory.
+--
+-- The directory returned is expected to be writable by the current user,
+-- but note that it isn't generally considered good practice to store
+-- application-specific data here; use 'getAppUserDataDirectory'
+-- instead.
+--
+-- On Unix, 'getUserDocumentsDirectory' returns the value of the @HOME@
+-- environment variable.  On Windows, the system is queried for a
+-- suitable path; a typical path might be
+-- @C:\/Documents and Settings\/user\/My Documents@.
+--
+-- The operation may fail with:
+--
+-- * 'UnsupportedOperation'
+-- The operating system has no notion of document directory.
+--
+-- * 'isDoesNotExistError'
+-- The document directory for the current user does not exist, or
+-- cannot be found.
 getUserDocumentsDirectory :: IO FilePath
 getUserDocumentsDirectory = fixPath <$> D.getUserDocumentsDirectory
 
-{- | Returns the current directory for temporary files.
-
-On Unix, 'getTemporaryDirectory' returns the value of the @TMPDIR@
-environment variable or \"\/tmp\" if the variable isn\'t defined.
-On Windows, the function checks for the existence of environment variables in
-the following order and uses the first path found:
-
-* TMP environment variable.
-
-* TEMP environment variable.
-
-* USERPROFILE environment variable.
-
-* The Windows directory
-
-The operation may fail with:
-
-* 'UnsupportedOperation'
-The operating system has no notion of temporary directory.
-
-The function doesn\'t verify whether the path exists.
--}
-
+-- | Returns the current directory for temporary files.
+--
+-- On Unix, 'getTemporaryDirectory' returns the value of the @TMPDIR@
+-- environment variable or \"\/tmp\" if the variable isn\'t defined.
+-- On Windows, the function checks for the existence of environment variables in
+-- the following order and uses the first path found:
+--
+-- * TMP environment variable.
+--
+-- * TEMP environment variable.
+--
+-- * USERPROFILE environment variable.
+--
+-- * The Windows directory
+--
+-- The operation may fail with:
+--
+-- * 'UnsupportedOperation'
+-- The operating system has no notion of temporary directory.
+--
+-- The function doesn\'t verify whether the path exists.
 getTemporaryDirectory :: IO FilePath
 getTemporaryDirectory = fixPath <$> D.getTemporaryDirectory
 
@@ -198,8 +185,7 @@
 ----------------------------------------------------------------
 -- Just adding documentation.
 
-{-|
-  This function copy the permission of the first file to the second.
--}
+-- |
+--   This function copy the permission of the first file to the second.
 copyPermissions :: FilePath -> FilePath -> IO ()
 copyPermissions = D.copyPermissions
diff --git a/System/EasyFile/FilePath.hs b/System/EasyFile/FilePath.hs
--- a/System/EasyFile/FilePath.hs
+++ b/System/EasyFile/FilePath.hs
@@ -10,33 +10,53 @@
 module System.EasyFile.FilePath (
     -- * Separator predicates
     FilePath,
-    pathSeparator, pathSeparators, isPathSeparator,
-{- xxx
-    searchPathSeparator, isSearchPathSeparator,
--}
-    extSeparator, isExtSeparator,
-
-{- xxx
-    -- * Path methods (environment $PATH)
-    splitSearchPath, getSearchPath,
--}
+    pathSeparator,
+    pathSeparators,
+    isPathSeparator,
+    {- xxx
+        searchPathSeparator, isSearchPathSeparator,
+    -}
+    extSeparator,
+    isExtSeparator,
+    {- xxx
+        -- * Path methods (environment $PATH)
+        splitSearchPath, getSearchPath,
+    -}
 
     -- * Extension methods
     splitExtension,
-    takeExtension, replaceExtension, dropExtension, addExtension, hasExtension, (<.>),
-    splitExtensions, dropExtensions, takeExtensions,
+    takeExtension,
+    replaceExtension,
+    dropExtension,
+    addExtension,
+    hasExtension,
+    (<.>),
+    splitExtensions,
+    dropExtensions,
+    takeExtensions,
 
     -- * Drive methods
-    splitDrive, joinDrive,
-    takeDrive, hasDrive, dropDrive, isDrive,
+    splitDrive,
+    joinDrive,
+    takeDrive,
+    hasDrive,
+    dropDrive,
+    isDrive,
 
     -- * Operations on a FilePath, as a list of directories
     splitFileName,
-    takeFileName, replaceFileName, dropFileName,
-    takeBaseName, replaceBaseName,
-    takeDirectory, replaceDirectory,
-    combine, (</>),
-    splitPath, joinPath, splitDirectories,
+    takeFileName,
+    replaceFileName,
+    dropFileName,
+    takeBaseName,
+    replaceBaseName,
+    takeDirectory,
+    replaceDirectory,
+    combine,
+    (</>),
+    splitPath,
+    joinPath,
+    splitDirectories,
 
     -- * Low level FilePath operators
     hasTrailingPathSeparator,
@@ -44,32 +64,28 @@
     dropTrailingPathSeparator,
 
     -- * File name manipulators
-    normalise, equalFilePath,
+    normalise,
+    equalFilePath,
     makeRelative,
-    isRelative, isAbsolute,
-{- xxx
-    isValid, makeValid
--}
+    isRelative,
+    isAbsolute,
 
 #ifdef TESTING
-    , isRelativeDrive
+    isRelativeDrive,
 #endif
-
-    )
-    where
+    {- xxx
+        isValid, makeValid
+    -}
+)
+where
 
-import Data.Char(toLower, toUpper)
-import Data.Maybe(isJust, fromJust)
+import Data.Char (toLower, toUpper)
+import Data.Maybe (fromJust, isJust)
 
 -- import System.Environment(getEnv) -- xxx
 
-
-infixr 7  <.>
-infixr 5  </>
-
-
-
-
+infixr 7 <.>
+infixr 5 </>
 
 ---------------------------------------------------------------------
 -- Platform Abstraction Methods (private)
@@ -111,7 +127,6 @@
 isPathSeparator :: Char -> Bool
 isPathSeparator = (`elem` pathSeparators)
 
-
 {- xxx
 -- | The character that is used to separate the entries in the $PATH environment variable.
 --
@@ -139,9 +154,6 @@
 isExtSeparator :: Char -> Bool
 isExtSeparator = (== extSeparator)
 
-
-
-
 {- xxx
 ---------------------------------------------------------------------
 -- Path methods (environment $PATH)
@@ -165,7 +177,6 @@
     g "" = ["." | isPosix]
     g x = [x]
 
-
 -- | Get a list of filepaths in the $PATH.
 getSearchPath :: IO [FilePath]
 getSearchPath = fmap splitSearchPath (getEnv "PATH")
@@ -187,11 +198,11 @@
 -- > splitExtension "file/path.txt/" == ("file/path.txt/","")
 splitExtension :: FilePath -> (String, String)
 splitExtension x = case d of
-                       "" -> (x,"")
-                       (y:ys) -> (a ++ reverse ys, y : reverse c)
-    where
-        (a,b) = splitFileName x
-        (c,d) = break isExtSeparator $ reverse b
+    "" -> (x, "")
+    (y : ys) -> (a ++ reverse ys, y : reverse c)
+  where
+    (a, b) = splitFileName x
+    (c, d) = break isExtSeparator $ reverse b
 
 -- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.
 --
@@ -232,12 +243,14 @@
 -- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"
 addExtension :: FilePath -> String -> FilePath
 addExtension file "" = file
-addExtension file xs@(x:_) = joinDrive a res
-    where
-        res = if isExtSeparator x then b ++ xs
-              else b ++ [extSeparator] ++ xs
+addExtension file xs@(x : _) = joinDrive a res
+  where
+    res =
+        if isExtSeparator x
+            then b ++ xs
+            else b ++ [extSeparator] ++ xs
 
-        (a,b) = splitDrive file
+    (a, b) = splitDrive file
 
 -- | Does the given filename have an extension?
 --
@@ -245,15 +258,14 @@
 hasExtension :: FilePath -> Bool
 hasExtension = any isExtSeparator . takeFileName
 
-
 -- | Split on all extensions
 --
 -- > splitExtensions "file.tar.gz" == ("file",".tar.gz")
 splitExtensions :: FilePath -> (FilePath, String)
 splitExtensions x = (a ++ c, d)
-    where
-        (a,b) = splitFileName x
-        (c,d) = break isExtSeparator b
+  where
+    (a, b) = splitFileName x
+    (c, d) = break isExtSeparator b
 
 -- | Drop all extensions
 --
@@ -267,8 +279,6 @@
 takeExtensions :: FilePath -> String
 takeExtensions = snd . splitExtensions
 
-
-
 ---------------------------------------------------------------------
 -- Drive methods
 
@@ -277,7 +287,6 @@
 isLetter :: Char -> Bool
 isLetter x = (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z')
 
-
 -- | Split a path into a drive and a path.
 --   On Unix, \/ is a Drive.
 --
@@ -296,56 +305,58 @@
 -- > Posix:   splitDrive "file" == ("","file")
 splitDrive :: FilePath -> (FilePath, FilePath)
 splitDrive x | isPosix = span (== '/') x
-
 splitDrive x | isJust y = fromJust y
-    where y = readDriveLetter x
-
+  where
+    y = readDriveLetter x
 splitDrive x | isJust y = fromJust y
-    where y = readDriveUNC x
-
+  where
+    y = readDriveUNC x
 splitDrive x | isJust y = fromJust y
-    where y = readDriveShare x
-
-splitDrive x = ("",x)
+  where
+    y = readDriveShare x
+splitDrive x = ("", x)
 
 addSlash :: FilePath -> FilePath -> (FilePath, FilePath)
-addSlash a xs = (a++c,d)
-    where (c,d) = span isPathSeparator xs
+addSlash a xs = (a ++ c, d)
+  where
+    (c, d) = span isPathSeparator xs
 
 -- http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
 -- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"
 -- a is "\\?\"
 readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)
-readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =
+readDriveUNC (s1 : s2 : '?' : s3 : xs) | all isPathSeparator [s1, s2, s3] =
     case map toUpper xs of
-        ('U':'N':'C':s4:_) | isPathSeparator s4 ->
-            let (a,b) = readDriveShareName (drop 4 xs)
-            in Just (s1:s2:'?':s3:take 4 xs ++ a, b)
+        ('U' : 'N' : 'C' : s4 : _)
+            | isPathSeparator s4 ->
+                let (a, b) = readDriveShareName (drop 4 xs)
+                 in Just (s1 : s2 : '?' : s3 : take 4 xs ++ a, b)
         _ -> case readDriveLetter xs of
-                 Just (a,b) -> Just (s1:s2:'?':s3:a,b)
-                 Nothing -> Nothing
+            Just (a, b) -> Just (s1 : s2 : '?' : s3 : a, b)
+            Nothing -> Nothing
 readDriveUNC _ = Nothing
 
 {- c:\ -}
 readDriveLetter :: String -> Maybe (FilePath, FilePath)
-readDriveLetter (x:':':y:xs) | isLetter x && isPathSeparator y = Just $ addSlash [x,':'] (y:xs)
-readDriveLetter (x:':':xs) | isLetter x = Just ([x,':'], xs)
+readDriveLetter (x : ':' : y : xs) | isLetter x && isPathSeparator y = Just $ addSlash [x, ':'] (y : xs)
+readDriveLetter (x : ':' : xs) | isLetter x = Just ([x, ':'], xs)
 readDriveLetter _ = Nothing
 
 {- \\sharename\ -}
 readDriveShare :: String -> Maybe (FilePath, FilePath)
-readDriveShare (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 =
-        Just (s1:s2:a,b)
-    where (a,b) = readDriveShareName xs
+readDriveShare (s1 : s2 : xs)
+    | isPathSeparator s1 && isPathSeparator s2 =
+        Just (s1 : s2 : a, b)
+  where
+    (a, b) = readDriveShareName xs
 readDriveShare _ = Nothing
 
 {- assume you have already seen \\ -}
 {- share\bob -> "share","\","bob" -}
 readDriveShareName :: String -> (FilePath, FilePath)
 readDriveShareName name = addSlash a b
-    where (a,b) = break isPathSeparator name
-
-
+  where
+    (a, b) = break isPathSeparator name
 
 -- | Join a drive and the rest of the path.
 --
@@ -355,13 +366,14 @@
 -- > Windows: joinDrive "\\\\share" "foo" == "\\\\share/foo" -- xxx
 -- > Windows: joinDrive "/:" "foo" == "/:/foo" -- xxx
 joinDrive :: FilePath -> FilePath -> FilePath
-joinDrive a b | isPosix = a ++ b
-              | null a = b
-              | null b = a
-              | isPathSeparator (last a) = a ++ b
-              | otherwise = case a of
-                                [a1,':'] | isLetter a1 -> a ++ b
-                                _ -> a ++ [pathSeparator] ++ b
+joinDrive a b
+    | isPosix = a ++ b
+    | null a = b
+    | null b = a
+    | isPathSeparator (last a) = a ++ b
+    | otherwise = case a of
+        [a1, ':'] | isLetter a1 -> a ++ b
+        _ -> a ++ [pathSeparator] ++ b
 
 -- | Get the drive from a filepath.
 --
@@ -381,12 +393,10 @@
 hasDrive :: FilePath -> Bool
 hasDrive = not . null . takeDrive
 
-
 -- | Is an element a drive
 isDrive :: FilePath -> Bool
 isDrive = null . dropDrive
 
-
 ---------------------------------------------------------------------
 -- Operations on a filepath, as a list of directories
 
@@ -401,10 +411,9 @@
 -- > Windows: splitFileName "c:" == ("c:","")
 splitFileName :: FilePath -> (String, String)
 splitFileName x = (c ++ reverse b, reverse a)
-    where
-        (a,b) = break isPathSeparator $ reverse d
-        (c,d) = splitDrive x
-
+  where
+    (a, b) = break isPathSeparator $ reverse d
+    (c, d) = splitDrive x
 
 -- | Set the filename.
 --
@@ -418,7 +427,6 @@
 dropFileName :: FilePath -> FilePath
 dropFileName = fst . splitFileName
 
-
 -- | Get the file name.
 --
 -- > takeFileName "test/" == ""
@@ -449,9 +457,9 @@
 -- > replaceBaseName x (takeBaseName x) == x
 replaceBaseName :: FilePath -> String -> FilePath
 replaceBaseName pth nam = combineAlways a (nam <.> ext)
-    where
-        (a,b) = splitFileName pth
-        ext = takeExtension b
+  where
+    (a, b) = splitFileName pth
+    ext = takeExtension b
 
 -- | Is an item either a directory or the last character a path separator?
 --
@@ -461,7 +469,6 @@
 hasTrailingPathSeparator "" = False
 hasTrailingPathSeparator x = isPathSeparator (last x)
 
-
 -- | Add a trailing file path separator if one is not already present.
 --
 -- > hasTrailingPathSeparator (addTrailingPathSeparator x)
@@ -470,7 +477,6 @@
 addTrailingPathSeparator :: FilePath -> FilePath
 addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pathSeparator]
 
-
 -- | Remove any trailing path separators
 --
 -- > dropTrailingPathSeparator "file/test/" == "file/test"
@@ -479,10 +485,10 @@
 dropTrailingPathSeparator :: FilePath -> FilePath
 dropTrailingPathSeparator x =
     if hasTrailingPathSeparator x && not (isDrive x)
-    then let x' = reverse $ dropWhile isPathSeparator $ reverse x
-         in if null x' then [pathSeparator] else x'
-    else x
-
+        then
+            let x' = reverse $ dropWhile isPathSeparator $ reverse x
+             in if null x' then [pathSeparator] else x'
+        else x
 
 -- | Get the directory name, move up one level.
 --
@@ -494,12 +500,16 @@
 -- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar" -- xxx
 -- > Windows:  takeDirectory "C:/" == "C:/"
 takeDirectory :: FilePath -> FilePath
-takeDirectory x = if isDrive file then file
-                  else if null res && not (null file) then file
-                  else res
-    where
-        res = reverse $ dropWhile isPathSeparator $ reverse file
-        file = dropFileName x
+takeDirectory x =
+    if isDrive file
+        then file
+        else
+            if null res && not (null file)
+                then file
+                else res
+  where
+    res = reverse $ dropWhile isPathSeparator $ reverse file
+    file = dropFileName x
 
 -- | Set the directory, keeping the filename the same.
 --
@@ -507,30 +517,29 @@
 replaceDirectory :: FilePath -> String -> FilePath
 replaceDirectory x dir = combineAlways dir (takeFileName x)
 
-
 -- | Combine two paths, if the second path 'isAbsolute', then it returns the second.
 --
 -- > Valid x => combine (takeDirectory x) (takeFileName x) `equalFilePath` x
 -- > combine "/" "test" == "/test"
 -- > combine "home" "bob" == "home/bob"
 combine :: FilePath -> FilePath -> FilePath
-combine a b | hasDrive b || (not (null b) && isPathSeparator (head b)) = b
-            | otherwise = combineAlways a b
+combine a b
+    | hasDrive b || (not (null b) && isPathSeparator (head b)) = b
+    | otherwise = combineAlways a b
 
 -- | Combine two paths, assuming rhs is NOT absolute.
 combineAlways :: FilePath -> FilePath -> FilePath
-combineAlways a b | null a = b
-                  | null b = a
-                  | isPathSeparator (last a) = a ++ b
-                  | isDrive a = joinDrive a b
-                  | otherwise = a ++ [pathSeparator] ++ b
-
+combineAlways a b
+    | null a = b
+    | null b = a
+    | isPathSeparator (last a) = a ++ b
+    | isDrive a = joinDrive a b
+    | otherwise = a ++ [pathSeparator] ++ b
 
 -- | A nice alias for 'combine'.
 (</>) :: FilePath -> FilePath -> FilePath
 (</>) = combine
 
-
 -- | Split a path by the directory separator.
 --
 -- > concat (splitPath x) == x
@@ -541,14 +550,14 @@
 -- > Posix:   splitPath "/file/test" == ["/","file/","test"]
 splitPath :: FilePath -> [FilePath]
 splitPath x = [drive | drive /= ""] ++ f path
-    where
-        (drive,path) = splitDrive x
+  where
+    (drive, path) = splitDrive x
 
-        f "" = []
-        f y = (a++c) : f d
-            where
-                (a,b) = break isPathSeparator y
-                (c,d) = break (not . isPathSeparator) b
+    f "" = []
+    f y = (a ++ c) : f d
+      where
+        (a, b) = break isPathSeparator y
+        (c, d) = break (not . isPathSeparator) b
 
 -- | Just as 'splitPath', but don't add the trailing slashes to each element.
 --
@@ -558,15 +567,16 @@
 -- > splitDirectories "" == []
 splitDirectories :: FilePath -> [FilePath]
 splitDirectories path =
-        if hasDrive path then head pathComponents : f (tail pathComponents)
+    if hasDrive path
+        then head pathComponents : f (tail pathComponents)
         else f pathComponents
-    where
-        pathComponents = splitPath path
-
-        f xs = map g xs
-        g x = if null res then x else res
-            where res = takeWhile (not . isPathSeparator) x
+  where
+    pathComponents = splitPath path
 
+    f xs = map g xs
+    g x = if null res then x else res
+      where
+        res = takeWhile (not . isPathSeparator) x
 
 -- | Join path elements back together.
 --
@@ -578,11 +588,6 @@
 joinPath :: [FilePath] -> FilePath
 joinPath x = foldr combine "" x
 
-
-
-
-
-
 ---------------------------------------------------------------------
 -- File name manipulators
 
@@ -599,13 +604,14 @@
 -- > Windows: equalFilePath "foo" "FOO"
 equalFilePath :: FilePath -> FilePath -> Bool
 equalFilePath a b = f a == f b
-    where
-        f x | isWindows = dropTrailSlash $ map toLower $ normalise x
-            | otherwise = dropTrailSlash $ normalise x
-
-        dropTrailSlash x | length x >= 2 && isPathSeparator (last x) = init x
-                         | otherwise = x
+  where
+    f x
+        | isWindows = dropTrailSlash $ map toLower $ normalise x
+        | otherwise = dropTrailSlash $ normalise x
 
+    dropTrailSlash x
+        | length x >= 2 && isPathSeparator (last x) = init x
+        | otherwise = x
 
 -- | Contract a filename, based on a relative path.
 --
@@ -627,24 +633,27 @@
 -- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"
 makeRelative :: FilePath -> FilePath -> FilePath
 makeRelative root path
- | equalFilePath root path = "."
- | takeAbs root /= takeAbs path = path
- | otherwise = f (dropAbs root) (dropAbs path)
-    where
-        f "" y = dropWhile isPathSeparator y
-        f x y = let (x1,x2) = g x
-                    (y1,y2) = g y
-                in if equalFilePath x1 y1 then f x2 y2 else path
+    | equalFilePath root path = "."
+    | takeAbs root /= takeAbs path = path
+    | otherwise = f (dropAbs root) (dropAbs path)
+  where
+    f "" y = dropWhile isPathSeparator y
+    f x y =
+        let (x1, x2) = g x
+            (y1, y2) = g y
+         in if equalFilePath x1 y1 then f x2 y2 else path
 
-        g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)
-            where (a,b) = break isPathSeparator $ dropWhile isPathSeparator x
+    g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)
+      where
+        (a, b) = break isPathSeparator $ dropWhile isPathSeparator x
 
-        -- on windows, need to drop '/' which is kind of absolute, but not a drive
-        dropAbs (x:xs) | isPathSeparator x = xs
-        dropAbs x = dropDrive x
+    -- on windows, need to drop '/' which is kind of absolute, but not a drive
+    dropAbs (x : xs) | isPathSeparator x = xs
+    dropAbs x = dropDrive x
 
-        takeAbs (x:_) | isPathSeparator x = [pathSeparator]
-        takeAbs x = map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x
+    takeAbs (x : _) | isPathSeparator x = [pathSeparator]
+    takeAbs x =
+        map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x
 
 -- | Normalise a file
 --
@@ -665,33 +674,35 @@
 -- > Windows: normalise "." == "."
 -- > Posix:   normalise "./" == "./"
 normalise :: FilePath -> FilePath
-normalise path = joinDrive (normaliseDrive drv) (f pth)
-              ++ [pathSeparator | not (null pth) && isPathSeparator (last pth)]
-    where
-        (drv,pth) = splitDrive path
+normalise path =
+    joinDrive (normaliseDrive drv) (f pth)
+        ++ [pathSeparator | not (null pth) && isPathSeparator (last pth)]
+  where
+    (drv, pth) = splitDrive path
 
-        f = joinPath . dropDots [] . splitDirectories . propSep
+    f = joinPath . dropDots [] . splitDirectories . propSep
 
-        propSep (a:b:xs)
-         | isPathSeparator a && isPathSeparator b = propSep (a:xs)
-        propSep (a:xs)
-         | isPathSeparator a = pathSeparator : propSep xs
-        propSep (x:xs) = x : propSep xs
-        propSep [] = []
+    propSep (a : b : xs)
+        | isPathSeparator a && isPathSeparator b = propSep (a : xs)
+    propSep (a : xs)
+        | isPathSeparator a = pathSeparator : propSep xs
+    propSep (x : xs) = x : propSep xs
+    propSep [] = []
 
-        dropDots acc (".":xs) | not $ null xs = dropDots acc xs
-        dropDots acc (x:xs) = dropDots (x:acc) xs
-        dropDots acc [] = reverse acc
+    dropDots acc ("." : xs) | not $ null xs = dropDots acc xs
+    dropDots acc (x : xs) = dropDots (x : acc) xs
+    dropDots acc [] = reverse acc
 
 normaliseDrive :: FilePath -> FilePath
 normaliseDrive drive | isPosix = drive
-normaliseDrive drive = if isJust $ readDriveLetter x2
-                       then map toUpper x2
-                       else drive
-    where
-        x2 = map repSlash drive
+normaliseDrive drive =
+    if isJust $ readDriveLetter x2
+        then map toUpper x2
+        else drive
+  where
+    x2 = map repSlash drive
 
-        repSlash x = if isPathSeparator x then pathSeparator else x
+    repSlash x = if isPathSeparator x then pathSeparator else x
 
 {- xxx
 -- information for validity functions on Windows
@@ -724,7 +735,6 @@
         x2 = dropDrive path
         f x = map toUpper (dropExtensions x) `elem` badElements
 
-
 -- | Take a FilePath and make it valid; does not change already valid FilePaths.
 --
 -- > isValid (makeValid x)
@@ -768,7 +778,6 @@
 isRelative :: FilePath -> Bool
 isRelative = isRelativeDrive . takeDrive
 
-
 -- > isRelativeDrive "" == True
 -- > Windows: isRelativeDrive "c:\\" == False
 -- > Windows: isRelativeDrive "c:/" == False
@@ -776,9 +785,9 @@
 -- > Windows: isRelativeDrive "\\\\foo" == False
 -- > Posix:   isRelativeDrive "/" == False
 isRelativeDrive :: String -> Bool
-isRelativeDrive x = null x ||
-    maybe False (not . isPathSeparator . last . fst) (readDriveLetter x)
-
+isRelativeDrive x =
+    null x
+        || maybe False (not . isPathSeparator . last . fst) (readDriveLetter x)
 
 -- | @not . 'isRelative'@
 --
diff --git a/System/EasyFile/Missing.hs b/System/EasyFile/Missing.hs
--- a/System/EasyFile/Missing.hs
+++ b/System/EasyFile/Missing.hs
@@ -4,9 +4,6 @@
 
 ----------------------------------------------------------------
 
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative
-#endif
 import Data.Time
 import Data.Time.Clock.POSIX
 import Data.Word (Word64)
@@ -22,10 +19,9 @@
 
 ----------------------------------------------------------------
 
-{-|
-  This function tells whether or not a file\/directory is symbolic
-  link.
--}
+-- |
+--   This function tells whether or not a file\/directory is symbolic
+--   link.
 isSymlink :: FilePath -> IO Bool
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 isSymlink _ = return False
@@ -33,9 +29,8 @@
 isSymlink file = isSymbolicLink <$> getSymbolicLinkStatus file
 #endif
 
-{-|
-  This function returns the link counter of a file\/directory.
--}
+-- |
+--   This function returns the link counter of a file\/directory.
 getLinkCount :: FilePath -> IO (Maybe Int)
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 getLinkCount _ = return Nothing
@@ -43,25 +38,23 @@
 getLinkCount file = Just . fromIntegral . linkCount <$> getFileStatus file
 #endif
 
-{-|
-  This function returns whether or not a directory has sub-directories.
--}
+-- |
+--   This function returns whether or not a directory has sub-directories.
+--   It returns 'Nothing' on Mac and Windows. Since it relies on the link
+--   counter, the result is wrong on file systems such as btrfs.
 hasSubDirectories :: FilePath -> IO (Maybe Bool)
 #ifdef darwin_HOST_OS
 hasSubDirectories _ = return Nothing
 #else
-hasSubDirectories file = do
-  Just n <- getLinkCount file
-  return $ Just (n > 2)
+hasSubDirectories file = fmap (> 2) <$> getLinkCount file
 #endif
 
 ----------------------------------------------------------------
 
-{-|
-The 'getCreationTime' operation returns the
-UTC time at which the file or directory was created.
-The time is only available on Windows.
--}
+-- |
+-- The 'getCreationTime' operation returns the
+-- UTC time at which the file or directory was created.
+-- The time is only available on Windows.
 getCreationTime :: FilePath -> IO (Maybe UTCTime)
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 getCreationTime file = Just . creationTime <$> fileTime file
@@ -69,13 +62,12 @@
 getCreationTime _ = return Nothing
 #endif
 
-{-|
-The 'getChangeTime' operation returns the
-UTC time at which the file or directory was changed.
-The time is only available on Unix and Mac.
-Note that Unix's rename() does not change ctime but
-MacOS's rename() does.
--}
+-- |
+-- The 'getChangeTime' operation returns the
+-- UTC time at which the file or directory was changed.
+-- The time is only available on Unix and Mac.
+-- Note that Unix's rename() does not change ctime but
+-- MacOS's rename() does.
 getChangeTime :: FilePath -> IO (Maybe UTCTime)
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 getChangeTime _ = return Nothing
@@ -83,18 +75,17 @@
 getChangeTime file = Just . epochTimeToUTCTime . statusChangeTime <$> getFileStatus file
 #endif
 
-{-|
-The 'getModificationTime' operation returns the
-UTC time at which the file or directory was last modified.
-
-The operation may fail with:
-
-* 'isPermissionError' if the user is not permitted to access
-  the modification time; or
-
-* 'isDoesNotExistError' if the file or directory does not exist.
-
--}
+-- |
+-- The 'getModificationTime' operation returns the
+-- UTC time at which the file or directory was last modified.
+-- Unlike the one in "System.Directory", the time is truncated to seconds.
+--
+-- The operation may fail with:
+--
+-- * 'isPermissionError' if the user is not permitted to access
+--   the modification time; or
+--
+-- * 'isDoesNotExistError' if the file or directory does not exist.
 getModificationTime :: FilePath -> IO UTCTime
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 getModificationTime file = writeTime <$> fileTime file
@@ -107,10 +98,11 @@
   The NTFS file system delays updates to the last access time for
   a file by up to 1 hour after the last access.
 -}
-{-|
-The 'getModificationTime' operation returns the
-UTC time at which the file or directory was last accessed.
--}
+
+-- |
+-- The 'getAccessTime' operation returns the
+-- UTC time at which the file or directory was last accessed.
+-- The time is truncated to seconds.
 getAccessTime :: FilePath -> IO UTCTime
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 getAccessTime file = accessTime <$> fileTime file
@@ -179,14 +171,12 @@
 --   Since: 0.2.4.
 setFileSize :: FilePath -> Word64 -> IO ()
 #if (defined(mingw32_HOST_OS) || defined(__MINGW32__))
-# if MIN_VERSION_Win32(2, 6, 2)
-setFileSize file siz = do
-    hdl <- createFile file gENERIC_WRITE fILE_SHARE_NONE Nothing oPEN_EXISTING fILE_ATTRIBUTE_NORMAL Nothing
-    _ <- setFilePointerEx hdl (fromIntegral siz) fILE_CURRENT
+setFileSize file siz = bracket setup closeHandle $ \hdl -> do
+    _ <- setFilePointerEx hdl (fromIntegral siz) fILE_BEGIN
     setEndOfFile hdl
-# else
-setFileSize _ _ = error "GHC 8.10.5 or earlier does not provide setFilePointerEx"
-# endif
+  where
+    setup = createFile file gENERIC_WRITE fILE_SHARE_NONE Nothing
+                       oPEN_EXISTING fILE_ATTRIBUTE_NORMAL Nothing
 #else
 setFileSize file siz = P.setFileSize file $ fromIntegral siz
 #endif
diff --git a/easy-file.cabal b/easy-file.cabal
--- a/easy-file.cabal
+++ b/easy-file.cabal
@@ -1,32 +1,25 @@
-Cabal-Version:          >= 1.10
+Cabal-Version:          2.4
 Name:                   easy-file
-Version:                0.2.5
+Version:                0.2.6
 Author:                 Kazu Yamamoto <kazu@iij.ad.jp>
 Maintainer:             Kazu Yamamoto <kazu@iij.ad.jp>
-License:                BSD3
+License:                BSD-3-Clause
 License-File:           LICENSE
 Synopsis:               Cross-platform File handling
 Description:            Cross-platform File handling for Unix\/Mac\/Windows
 Homepage:               http://github.com/kazu-yamamoto/easy-file
 Category:               System
 Build-Type:             Simple
+Extra-Doc-Files:        ChangeLog.md README.md
 
 Tested-With:
-  GHC == 9.6.0
-  GHC == 9.4.4
-  GHC == 9.2.7
-  GHC == 9.0.2
-  GHC == 8.10.7
-  GHC == 8.8.4
-  GHC == 8.6.5
-  GHC == 8.4.4
-  GHC == 8.2.2
-  GHC == 8.0.2
-  GHC == 7.10.3
-  GHC == 7.8.4
-  GHC == 7.6.3
-  GHC == 7.4.2
-  GHC == 7.0.4
+  GHC == 9.14.1
+  GHC == 9.12.4
+  GHC == 9.10.3
+  GHC == 9.8.4
+  GHC == 9.6.7
+  GHC == 9.4.8
+  GHC == 9.2.8
 
 Library
   Exposed-Modules:      System.EasyFile
@@ -34,7 +27,7 @@
                         System.EasyFile.Directory
                         System.EasyFile.Missing
   Build-Depends:
-      base >= 4 && < 5
+      base >= 4.16 && < 5
     , directory
     , filepath
     , time
@@ -45,9 +38,7 @@
     Build-Depends:      unix
 
   Default-Language:     Haskell2010
-  GHC-Options:          -Wall
-  if impl(ghc >= 8)
-    GHC-Options:        -Wcompat
+  GHC-Options:          -Wall -Wcompat
 
 
 Test-Suite test
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -2,15 +2,27 @@
 
 module Main where
 
+import Control.Exception (bracket)
 import System.EasyFile
+import System.IO
 import Test.Tasty
 import Test.Tasty.HUnit
 
 main :: IO ()
-main = defaultMain $
-  testCase "easy-file tests" $ do
+main =
+    defaultMain $
+        testGroup
+            "easy-file"
+            [ testCase "path" testPath
+            , testCase "setFileSize" testSetFileSize
+            , testCase "hasSubDirectories" testHasSubDirectories
+            , testCase "metadata of a file open for writing" testOpenForWriting
+            ]
 
-    assertEqual "Test that '/' is path separator"
+testPath :: Assertion
+testPath = do
+    assertEqual
+        "Test that '/' is path separator"
         ("foo" </> "bar")
         "foo/bar"
 
@@ -22,10 +34,55 @@
 
     isRelative "c:/foo" @?= not isWindows
 
+-- The file must not be left open: getting its size and removing it work.
+testSetFileSize :: Assertion
+testSetFileSize = withTempDir "setFileSize" $ \dir -> do
+    let file = dir </> "file"
+    writeFile file "0123456789"
+    setFileSize file 4
+    getFileSize file >>= (@?= 4)
+    setFileSize file 16
+    getFileSize file >>= (@?= 16)
+    removeFile file
+
+testHasSubDirectories :: Assertion
+testHasSubDirectories = withTempDir "hasSubDirectories" $ \dir -> do
+    createDirectory $ dir </> "sub"
+    r <- hasSubDirectories dir
+    if isWindows || isMac
+        then r @?= Nothing
+        else r @?= Just True
+
+testOpenForWriting :: Assertion
+testOpenForWriting = withTempDir "openForWriting" $ \dir -> do
+    let file = dir </> "file"
+    withFile file WriteMode $ \h -> do
+        hPutStr h "0123456789" >> hFlush h
+        _ <- getModificationTime file
+        _ <- getAccessTime file
+        getFileSize file >>= (@?= 10)
+
+withTempDir :: String -> (FilePath -> IO a) -> IO a
+withTempDir name = bracket setup removeDirectoryRecursive
+  where
+    setup = do
+        tmp <- getTemporaryDirectory
+        let dir = tmp </> ("easy-file-test-" ++ name)
+        exist <- doesDirectoryExist dir
+        if exist then removeDirectoryRecursive dir else return ()
+        createDirectory dir
+        return dir
+
 isWindows :: Bool
-isWindows =
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-    True
+isWindows = True
 #else
-    False
+isWindows = False
+#endif
+
+isMac :: Bool
+#ifdef darwin_HOST_OS
+isMac = True
+#else
+isMac = False
 #endif
