diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,25 @@
+*Path IO follows [SemVer](https://semver.org/).*
+
+## Path IO 1.8.2
+
+* Maintenance release, compatibility with GHC 9.6, 9.8, and 9.10.
+
+## Path IO 1.8.1
+
+* Fixed a “permission denied” error occurring when `copyDirRecur` is used to
+  copy a tree with non-empty read-only directories. [PR
+  82](https://github.com/mrkkrp/path-io/pull/82).
+
+## Path IO 1.8.0
+
+* Added instances of `AnyPath` for `SomeBase`. [PR
+  72](https://github.com/mrkkrp/path-io/pull/72).
+
+## Path IO 1.7.0
+
+* Added `doesPathExist`, `getFileSize`, `renamePath`, and
+  `removePathForcibly`.
+
 ## Path IO 1.6.3
 
 * Fixed a bug that caused `removeDirLink` fail on Linux because of a
diff --git a/Path/IO.hs b/Path/IO.hs
--- a/Path/IO.hs
+++ b/Path/IO.hs
@@ -23,7 +23,9 @@
     ensureDir,
     removeDir,
     removeDirRecur,
+    removePathForcibly,
     renameDir,
+    renamePath,
     listDir,
     listDirRel,
     listDirRecur,
@@ -64,6 +66,7 @@
     removeFile,
     renameFile,
     copyFile,
+    getFileSize,
     findExecutable,
     findFile,
     findFiles,
@@ -86,6 +89,7 @@
     createTempDir,
 
     -- * Existence tests
+    doesPathExist,
     doesFileExist,
     doesDirExist,
     isLocationOccupied,
@@ -122,19 +126,19 @@
 import Control.Monad.Trans.Class (lift)
 import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)
 import Control.Monad.Trans.Writer.Strict (WriterT, execWriterT, tell)
-import qualified Data.DList as DList
+import Data.DList qualified as DList
 import Data.Either (lefts, rights)
 import Data.Kind (Type)
 import Data.List ((\\))
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Data.Time (UTCTime)
 import Path
-import qualified System.Directory as D
-import qualified System.FilePath as F
+import System.Directory qualified as D
+import System.FilePath qualified as F
 import System.IO (Handle)
 import System.IO.Error (isDoesNotExistError)
-import qualified System.IO.Temp as T
-import qualified System.PosixCompat.Files as P
+import System.IO.Temp qualified as T
+import System.PosixCompat.Files qualified as P
 
 ----------------------------------------------------------------------------
 -- Actions on directories
@@ -171,14 +175,14 @@
 -- * 'InappropriateType'
 -- The path refers to an existing non-directory object.
 -- @[EEXIST]@
-createDir :: MonadIO m => Path b Dir -> m ()
+createDir :: (MonadIO m) => Path b Dir -> m ()
 createDir = liftD D.createDirectory
 
 -- | @'createDirIfMissing' parents dir@ creates a new directory @dir@ if it
 -- doesn't exist. If the first argument is 'True' the function will also
 -- create all parent directories if they are missing.
 createDirIfMissing ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Create its parents too?
   Bool ->
   -- | The path to the directory you want to make
@@ -192,7 +196,7 @@
 -- > ensureDir = createDirIfMissing True
 --
 -- @since 0.3.1
-ensureDir :: MonadIO m => Path b Dir -> m ()
+ensureDir :: (MonadIO m) => Path b Dir -> m ()
 ensureDir = createDirIfMissing True
 
 -- | @'removeDir' dir@ removes an existing directory @dir@. The
@@ -232,15 +236,36 @@
 -- * 'InappropriateType'
 -- The operand refers to an existing non-directory object.
 -- @[ENOTDIR]@
-removeDir :: MonadIO m => Path b Dir -> m ()
+removeDir :: (MonadIO m) => Path b Dir -> m ()
 removeDir = liftD D.removeDirectory
 
 -- | @'removeDirRecur' dir@ removes an existing directory @dir@ together
 -- with its contents and sub-directories. Within this directory, symbolic
 -- links are removed without affecting their targets.
-removeDirRecur :: MonadIO m => Path b Dir -> m ()
+removeDirRecur :: (MonadIO m) => Path b Dir -> m ()
 removeDirRecur = liftD D.removeDirectoryRecursive
 
+-- | Remove a file or directory at /path/ together with its contents and
+-- subdirectories. Symbolic links are removed without affecting their
+-- targets. If the path does not exist, nothing happens.
+--
+-- Unlike other removal functions, this function will also attempt to delete
+-- files marked as read-only or otherwise made unremovable due to permissions.
+-- As a result, if the removal is incomplete, the permissions or attributes on
+-- the remaining files may be altered.  If there are hard links in the
+-- directory, then permissions on all related hard links may be altered.
+--
+-- If an entry within the directory vanishes while @removePathForcibly@ is
+-- running, it is silently ignored.
+--
+-- If an exception occurs while removing an entry, @removePathForcibly@ will
+-- still try to remove as many entries as it can before failing with an
+-- exception.  The first exception that it encountered is re-thrown.
+--
+-- @since 1.7.0
+removePathForcibly :: (MonadIO m) => Path b t -> m ()
+removePathForcibly = liftD D.removePathForcibly
+
 -- | @'renameDir' old new@ changes the name of an existing directory from
 --  @old@ to @new@. If the @new@ directory already exists, it is atomically
 --  replaced by the @old@ directory. If the @new@ directory is neither the
@@ -287,7 +312,7 @@
 --  Either path refers to an existing non-directory object.
 --  @[ENOTDIR, EISDIR]@
 renameDir ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Old name
   Path b0 Dir ->
   -- | New name
@@ -295,6 +320,51 @@
   m ()
 renameDir = liftD2 D.renameDirectory
 
+-- | Rename a file or directory.  If the destination path already exists, it
+-- is replaced atomically.  The destination path must not point to an existing
+-- directory.  A conformant implementation need not support renaming files in
+-- all situations (e.g. renaming across different physical devices), but the
+-- constraints must be documented.
+--
+-- The operation may fail with:
+--
+-- * @HardwareFault@
+-- A physical I\/O error has occurred.
+-- @[EIO]@
+--
+-- * @InvalidArgument@
+-- Either operand is not a valid file name.
+-- @[ENAMETOOLONG, ELOOP]@
+--
+-- * 'isDoesNotExistError'
+-- The original file does not exist, or there is no path to the target.
+-- @[ENOENT, ENOTDIR]@
+--
+-- * 'isPermissionError'
+-- The process has insufficient privileges to perform the operation.
+-- @[EROFS, EACCES, EPERM]@
+--
+-- * 'System.IO.isFullError'
+-- Insufficient resources are available to perform the operation.
+-- @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
+--
+-- * @UnsatisfiedConstraints@
+-- Implementation-dependent constraints are not satisfied.
+-- @[EBUSY]@
+--
+-- * @UnsupportedOperation@
+-- The implementation does not support renaming in this situation.
+-- @[EXDEV]@
+--
+-- * @InappropriateType@
+-- Either the destination path refers to an existing directory, or one of the
+-- parent segments in the destination path is not a directory.
+-- @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
+--
+-- @since 1.7.0
+renamePath :: (MonadIO m) => Path b0 t -> Path b1 t -> m ()
+renamePath = liftD2 D.renamePath
+
 -- | @'listDir' dir@ returns a list of /all/ entries in @dir@ without the
 -- special entries (@.@ and @..@). Entries are not sorted.
 --
@@ -324,7 +394,7 @@
 --   The path refers to an existing non-directory object.
 --   @[ENOTDIR]@
 listDir ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Directory to list
   Path b Dir ->
   -- | Sub-directories and files
@@ -341,7 +411,7 @@
 --
 -- @since 1.4.0
 listDirRel ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Directory to list
   Path b Dir ->
   -- | Sub-directories and files
@@ -361,7 +431,7 @@
 --
 -- __Note__: before version /1.3.0/, this function followed symlinks.
 listDirRecur ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Directory to list
   Path b Dir ->
   -- | Sub-directories and files
@@ -383,7 +453,7 @@
 --
 -- @since 1.4.2
 listDirRecurRel ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Directory to list
   Path b Dir ->
   -- | Sub-directories and files
@@ -417,7 +487,7 @@
 -- traversing symlinked directories, but recreating symlinks in the target
 -- directory according to their targets in the source directory.
 copyDirRecur ::
-  (MonadIO m, MonadCatch m) =>
+  (MonadIO m) =>
   -- | Source
   Path b0 Dir ->
   -- | Destination
@@ -442,7 +512,7 @@
 -- traversing symlinked directories, but recreating symlinks in the target
 -- directory according to their targets in the source directory.
 copyDirRecur' ::
-  (MonadIO m, MonadCatch m) =>
+  (MonadIO m) =>
   -- | Source
   Path b0 Dir ->
   -- | Destination
@@ -454,7 +524,7 @@
 -- to preserve directory permissions or not. /Does not/ follow symbolic
 -- links. Internal function.
 copyDirRecurGen ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Should we preserve directory permissions?
   Bool ->
   -- | Source
@@ -475,7 +545,7 @@
         (new </>)
           <$> stripProperPrefix old path
   ensureDir bdest
-  forM_ dirs $ \srcDir -> do
+  copyPermissionsIOs <- forM dirs $ \srcDir -> do
     destDir <- swapParent bsrc bdest srcDir
     dirIsSymlink <- isSymlink srcDir
     if dirIsSymlink
@@ -484,8 +554,7 @@
         D.createDirectoryLink target $
           toFilePath' destDir
       else ensureDir destDir
-    when preserveDirPermissions $
-      ignoringIOErrors (copyPermissions srcDir destDir)
+    pure $ ignoringIOErrors (copyPermissions srcDir destDir)
   forM_ files $ \srcFile -> do
     destFile <- swapParent bsrc bdest srcFile
     fileIsSymlink <- isSymlink srcFile
@@ -494,8 +563,9 @@
         target <- getSymlinkTarget srcFile
         D.createFileLink target (toFilePath destFile)
       else copyFile srcFile destFile
-  when preserveDirPermissions $
+  when preserveDirPermissions $ do
     ignoringIOErrors (copyPermissions bsrc bdest)
+    sequence_ copyPermissionsIOs
 
 ----------------------------------------------------------------------------
 -- Walking directory trees
@@ -556,7 +626,7 @@
 --
 -- @since 1.2.0
 walkDir ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Handler (@dir -> subdirs -> files -> 'WalkAction'@)
   (Path Abs Dir -> [Path Abs Dir] -> [Path Abs File] -> m (WalkAction Abs)) ->
   -- | Directory where traversal begins
@@ -598,7 +668,7 @@
 --
 -- @since 1.4.2
 walkDirRel ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Handler (@dir -> subdirs -> files -> 'WalkAction'@)
   ( Path Rel Dir ->
     [Path Rel Dir] ->
@@ -739,7 +809,7 @@
 --
 -- * 'UnsupportedOperation'
 -- The operating system has no notion of current working directory.
-getCurrentDir :: MonadIO m => m (Path Abs Dir)
+getCurrentDir :: (MonadIO m) => m (Path Abs Dir)
 getCurrentDir = liftIO $ D.getCurrentDirectory >>= parseAbsDir
 
 -- | Change the working directory to the given path.
@@ -774,7 +844,7 @@
 -- * 'InappropriateType'
 -- The path refers to an existing non-directory object.
 -- @[ENOTDIR]@
-setCurrentDir :: MonadIO m => Path b Dir -> m ()
+setCurrentDir :: (MonadIO m) => Path b Dir -> m ()
 setCurrentDir = liftD D.setCurrentDirectory
 
 -- | Run an 'IO' action with the given working directory and restore the
@@ -814,7 +884,7 @@
 -- * 'isDoesNotExistError'
 -- The home directory for the current user does not exist, or
 -- cannot be found.
-getHomeDir :: MonadIO m => m (Path Abs Dir)
+getHomeDir :: (MonadIO m) => m (Path Abs Dir)
 getHomeDir = liftIO D.getHomeDirectory >>= resolveDir'
 
 -- | Obtain the path to a special directory for storing user-specific
@@ -841,7 +911,7 @@
 --   The home directory for the current user does not exist, or cannot be
 --   found.
 getAppUserDataDir ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Name of application (used in path construction)
   String ->
   m (Path Abs Dir)
@@ -865,7 +935,7 @@
 -- * 'isDoesNotExistError'
 -- The document directory for the current user does not exist, or
 -- cannot be found.
-getUserDocsDir :: MonadIO m => m (Path Abs Dir)
+getUserDocsDir :: (MonadIO m) => m (Path Abs Dir)
 getUserDocsDir = liftIO $ D.getUserDocumentsDirectory >>= parseAbsDir
 
 -- | Return the current directory for temporary files.
@@ -893,7 +963,7 @@
 -- The operating system has no notion of temporary directory.
 --
 -- The function doesn't verify whether the path exists.
-getTempDir :: MonadIO m => m (Path Abs Dir)
+getTempDir :: (MonadIO m) => m (Path Abs Dir)
 getTempDir = liftIO D.getTemporaryDirectory >>= resolveDir'
 
 -- | Obtain the paths to special directories for storing user-specific
@@ -913,7 +983,7 @@
 --
 -- @since 1.2.1
 getXdgDir ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Which special directory
   D.XdgDirectory ->
   -- | A relative path that is appended to the path; if 'Nothing', the
@@ -933,7 +1003,7 @@
 --
 -- @since 1.5.0
 getXdgDirList ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Which special directory list
   D.XdgDirectoryList ->
   m [Path Abs Dir]
@@ -981,7 +1051,7 @@
   -- Please note that before version 1.2.3.0 of the @directory@ package,
   -- this function had unpredictable behavior on non-existent paths.
   canonicalizePath ::
-    MonadIO m =>
+    (MonadIO m) =>
     path ->
     m (AbsPath path)
 
@@ -991,7 +1061,7 @@
   -- If the path is already absolute, the operation never fails. Otherwise,
   -- the operation may fail with the same exceptions as 'getCurrentDir'.
   makeAbsolute ::
-    MonadIO m =>
+    (MonadIO m) =>
     path ->
     m (AbsPath path)
 
@@ -999,7 +1069,7 @@
   --
   -- @since 0.3.0
   makeRelative ::
-    MonadThrow m =>
+    (MonadThrow m) =>
     -- | Base directory
     Path Abs Dir ->
     -- | Path that will be made relative to base directory
@@ -1010,7 +1080,7 @@
   --
   -- @since 0.3.0
   makeRelativeToCurrentDir ::
-    MonadIO m =>
+    (MonadIO m) =>
     path ->
     m (RelPath path)
 
@@ -1032,12 +1102,54 @@
   makeRelative b p = parseRelDir (F.makeRelative (toFilePath b) (toFilePath p))
   makeRelativeToCurrentDir p = liftIO $ getCurrentDir >>= flip makeRelative p
 
+-- | @since 1.8.0
+instance AnyPath (SomeBase File) where
+  type AbsPath (SomeBase File) = Path Abs File
+  type RelPath (SomeBase File) = Path Rel File
+
+  canonicalizePath s = case s of
+    Abs a -> canonicalizePath a
+    Rel a -> canonicalizePath a
+
+  makeAbsolute s = case s of
+    Abs a -> makeAbsolute a
+    Rel a -> makeAbsolute a
+
+  makeRelative r s = case s of
+    Abs a -> makeRelative r a
+    Rel a -> makeRelative r a
+
+  makeRelativeToCurrentDir s = case s of
+    Abs a -> makeRelativeToCurrentDir a
+    Rel a -> makeRelativeToCurrentDir a
+
+-- | @since 1.8.0
+instance AnyPath (SomeBase Dir) where
+  type AbsPath (SomeBase Dir) = Path Abs Dir
+  type RelPath (SomeBase Dir) = Path Rel Dir
+
+  canonicalizePath s = case s of
+    Abs a -> canonicalizePath a
+    Rel a -> canonicalizePath a
+
+  makeAbsolute s = case s of
+    Abs a -> makeAbsolute a
+    Rel a -> makeAbsolute a
+
+  makeRelative r s = case s of
+    Abs a -> makeRelative r a
+    Rel a -> makeRelative r a
+
+  makeRelativeToCurrentDir s = case s of
+    Abs a -> makeRelativeToCurrentDir a
+    Rel a -> makeRelativeToCurrentDir a
+
 -- | Append stringly-typed path to an absolute path and then canonicalize
 -- it.
 --
 -- @since 0.3.0
 resolveFile ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Base directory
   Path Abs Dir ->
   -- | Path to resolve
@@ -1049,7 +1161,7 @@
 --
 -- @since 0.3.0
 resolveFile' ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Path to resolve
   FilePath ->
   m (Path Abs File)
@@ -1059,7 +1171,7 @@
 --
 -- @since 0.3.0
 resolveDir ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Base directory
   Path Abs Dir ->
   -- | Path to resolve
@@ -1071,7 +1183,7 @@
 --
 -- @since 0.3.0
 resolveDir' ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Path to resolve
   FilePath ->
   m (Path Abs Dir)
@@ -1110,7 +1222,7 @@
 -- * 'InappropriateType'
 -- The operand refers to an existing directory.
 -- @[EPERM, EINVAL]@
-removeFile :: MonadIO m => Path b File -> m ()
+removeFile :: (MonadIO m) => Path b File -> m ()
 removeFile = liftD D.removeFile
 
 -- | @'renameFile' old new@ changes the name of an existing file system
@@ -1154,7 +1266,7 @@
 -- Either path refers to an existing directory.
 -- @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
 renameFile ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Original location
   Path b0 File ->
   -- | New location
@@ -1167,7 +1279,7 @@
 -- file. Neither path may refer to an existing directory. The permissions of
 -- @old@ are copied to @new@, if possible.
 copyFile ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Original location
   Path b0 File ->
   -- | Where to put copy
@@ -1175,6 +1287,12 @@
   m ()
 copyFile = liftD2 D.copyFile
 
+-- | Obtain the size of a file in bytes.
+--
+-- @since 1.7.0
+getFileSize :: (MonadIO m) => Path b File -> m Integer
+getFileSize = liftD D.getFileSize
+
 -- | Given an executable file name, search for such file in the directories
 -- listed in system @PATH@. The returned value is the path to the found
 -- executable or 'Nothing' if an executable with the given name was not
@@ -1190,7 +1308,7 @@
 -- the directory containing the current executable. See
 -- <http://msdn.microsoft.com/en-us/library/aa365527.aspx> for more details.
 findExecutable ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Executable file name
   Path Rel File ->
   -- | Path to found executable
@@ -1199,7 +1317,7 @@
 
 -- | Search through the given set of directories for the given file.
 findFile ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Set of directories to search in
   [Path b Dir] ->
   -- | Filename of interest
@@ -1217,7 +1335,7 @@
 -- | Search through the given set of directories for the given file and
 -- return a list of paths where the given file exists.
 findFiles ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Set of directories to search in
   [Path b Dir] ->
   -- | Filename of interest
@@ -1230,7 +1348,7 @@
 -- the given property (usually permissions) and return a list of paths where
 -- the given file exists and has the property.
 findFilesWith ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | How to test the files
   (Path Abs File -> m Bool) ->
   -- | Set of directories to search in
@@ -1279,7 +1397,7 @@
 --
 -- @since 1.5.0
 createFileLink ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Path to the target file
   Path b0 File ->
   -- | Path to the link to be created
@@ -1316,7 +1434,7 @@
 --
 -- @since 1.5.0
 createDirLink ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Path to the target directory
   Path b0 Dir ->
   -- | Path to the link to be created
@@ -1336,7 +1454,7 @@
 --
 -- @since 1.5.0
 removeDirLink ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Path to the link to be removed
   Path b Dir ->
   m ()
@@ -1355,7 +1473,7 @@
 --
 -- @since 1.5.0
 getSymlinkTarget ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Symlink path
   Path b t ->
   m FilePath
@@ -1373,7 +1491,7 @@
 -- | Check if the given path is a symbolic link.
 --
 -- @since 1.3.0
-isSymlink :: MonadIO m => Path b t -> m Bool
+isSymlink :: (MonadIO m) => Path b t -> m Bool
 isSymlink = liftD D.pathIsSymbolicLink
 
 ----------------------------------------------------------------------------
@@ -1471,7 +1589,7 @@
 --
 -- @since 0.2.0
 openTempFile ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Directory to create file in
   Path b Dir ->
   -- | File name template; if the template is "foo.ext" then the created
@@ -1494,7 +1612,7 @@
 --
 -- @since 0.2.0
 openBinaryTempFile ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Directory to create file in
   Path b Dir ->
   -- | File name template, see 'openTempFile'
@@ -1514,7 +1632,7 @@
 --
 -- @since 0.2.0
 createTempDir ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Directory to create file in
   Path b Dir ->
   -- | Directory name template, see 'openTempFile'
@@ -1529,19 +1647,27 @@
 ----------------------------------------------------------------------------
 -- Existence tests
 
+-- | Test whether the given path points to an existing filesystem object. If
+-- the user lacks necessary permissions to search the parent directories,
+-- this function may return false even if the file does actually exist.
+--
+-- @since 1.7.0
+doesPathExist :: (MonadIO m) => Path b t -> m Bool
+doesPathExist = liftD D.doesPathExist
+
 -- | The operation 'doesFileExist' returns 'True' if the argument file
 -- exists and is not a directory, and 'False' otherwise.
-doesFileExist :: MonadIO m => Path b File -> m Bool
+doesFileExist :: (MonadIO m) => Path b File -> m Bool
 doesFileExist = liftD D.doesFileExist
 
 -- | The operation 'doesDirExist' returns 'True' if the argument file exists
 -- and is either a directory or a symbolic link to a directory, and 'False'
 -- otherwise.
-doesDirExist :: MonadIO m => Path b Dir -> m Bool
+doesDirExist :: (MonadIO m) => Path b Dir -> m Bool
 doesDirExist = liftD D.doesDirectoryExist
 
 -- | Check if there is a file or directory on specified path.
-isLocationOccupied :: MonadIO m => Path b t -> m Bool
+isLocationOccupied :: (MonadIO m) => Path b t -> m Bool
 isLocationOccupied path = do
   let fp = toFilePath path
   file <- liftIO (D.doesFileExist fp)
@@ -1578,7 +1704,7 @@
 --   the permissions; or
 --
 -- * 'isDoesNotExistError' if the file or directory does not exist.
-getPermissions :: MonadIO m => Path b t -> m D.Permissions
+getPermissions :: (MonadIO m) => Path b t -> m D.Permissions
 getPermissions = liftD D.getPermissions
 
 -- | The 'setPermissions' operation sets the permissions for the file or
@@ -1590,13 +1716,13 @@
 --   the permissions; or
 --
 -- * 'isDoesNotExistError' if the file or directory does not exist.
-setPermissions :: MonadIO m => Path b t -> D.Permissions -> m ()
+setPermissions :: (MonadIO m) => Path b t -> D.Permissions -> m ()
 setPermissions = liftD2' D.setPermissions
 
 -- | Set permissions for the object found on second given path so they match
 -- permissions of the object on the first path.
 copyPermissions ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | From where to copy
   Path b0 t0 ->
   -- | What to modify
@@ -1622,7 +1748,7 @@
 --
 -- Note: this is a piece of conditional API, only available if
 -- @directory-1.2.3.0@ or later is used.
-getAccessTime :: MonadIO m => Path b t -> m UTCTime
+getAccessTime :: (MonadIO m) => Path b t -> m UTCTime
 getAccessTime = liftD D.getAccessTime
 
 -- | Change the time at which the file or directory was last accessed.
@@ -1649,7 +1775,7 @@
 --
 -- Note: this is a piece of conditional API, only available if
 -- @directory-1.2.3.0@ or later is used.
-setAccessTime :: MonadIO m => Path b t -> UTCTime -> m ()
+setAccessTime :: (MonadIO m) => Path b t -> UTCTime -> m ()
 setAccessTime = liftD2' D.setAccessTime
 
 -- | Change the time at which the file or directory was last modified.
@@ -1676,7 +1802,7 @@
 --
 -- Note: this is a piece of conditional API, only available if
 -- @directory-1.2.3.0@ or later is used.
-setModificationTime :: MonadIO m => Path b t -> UTCTime -> m ()
+setModificationTime :: (MonadIO m) => Path b t -> UTCTime -> m ()
 setModificationTime = liftD2' D.setModificationTime
 
 -- | Obtain the time at which the file or directory was last modified.
@@ -1691,7 +1817,7 @@
 -- Caveat for POSIX systems: This function returns a timestamp with
 -- sub-second resolution only if this package is compiled against
 -- @unix-2.6.0.0@ or later and the underlying filesystem supports them.
-getModificationTime :: MonadIO m => Path b t -> m UTCTime
+getModificationTime :: (MonadIO m) => Path b t -> m UTCTime
 getModificationTime = liftD D.getModificationTime
 
 ----------------------------------------------------------------------------
@@ -1700,7 +1826,7 @@
 -- | Lift an action in 'IO' that takes 'FilePath' into an action in slightly
 -- more abstract monad that takes 'Path'.
 liftD ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Original action
   (FilePath -> IO a) ->
   -- | 'Path' argument
@@ -1712,7 +1838,7 @@
 
 -- | Similar to 'liftD' but for functions with arity 2.
 liftD2 ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Original action
   (FilePath -> FilePath -> IO a) ->
   -- | First 'Path' argument
@@ -1726,7 +1852,7 @@
 -- | Similar to 'liftD2', but allows us to pass second argument of arbitrary
 -- type.
 liftD2' ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Original action
   (FilePath -> v -> IO a) ->
   -- | First 'Path' argument
@@ -1745,5 +1871,5 @@
 ignoringIOErrors :: IO () -> IO ()
 ignoringIOErrors ioe = ioe `catch` handler
   where
-    handler :: Monad m => IOError -> m ()
+    handler :: (Monad m) => IOError -> m ()
     handler = const (return ())
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Main (main) where
-
-import Distribution.Simple
-
-main :: IO ()
-main = defaultMain
diff --git a/path-io.cabal b/path-io.cabal
--- a/path-io.cabal
+++ b/path-io.cabal
@@ -1,11 +1,11 @@
-cabal-version:   1.18
+cabal-version:   2.4
 name:            path-io
-version:         1.6.3
-license:         BSD3
+version:         1.8.2
+license:         BSD-3-Clause
 license-file:    LICENSE.md
 maintainer:      Mark Karpov <markkarpov92@gmail.com>
 author:          Mark Karpov <markkarpov92@gmail.com>
-tested-with:     ghc ==8.8.4 ghc ==8.10.4 ghc ==9.0.1
+tested-with:     ghc ==9.6.5 ghc ==9.8.2 ghc ==9.10.1
 homepage:        https://github.com/mrkkrp/path-io
 bug-reports:     https://github.com/mrkkrp/path-io/issues
 synopsis:        Interface to ‘directory’ package for users of ‘path’
@@ -27,49 +27,48 @@
 
 library
     exposed-modules:  Path.IO
-    default-language: Haskell2010
+    default-language: GHC2021
     build-depends:
-        base >=4.13 && <5.0,
+        base >=4.15 && <5,
         containers,
         directory >=1.3.2.0 && <1.4,
-        dlist >=0.8 && <2.0,
+        dlist >=0.8 && <2,
         exceptions >=0.8 && <0.11,
-        filepath >=1.2 && <1.5,
-        path >=0.6 && <0.9,
+        filepath >=1.2 && <1.6,
+        path >=0.7.1 && <0.10,
         temporary >=1.1 && <1.4,
-        time >=1.4 && <1.11,
-        transformers >=0.3 && <0.6,
+        time >=1.4 && <1.15,
+        transformers >=0.3 && <0.7,
         unix-compat
 
     if flag(dev)
-        ghc-options: -Wall -Werror
+        ghc-options:
+            -Wall -Werror -Wredundant-constraints -Wpartial-fields
+            -Wunused-packages
 
     else
         ghc-options: -O2 -Wall
 
-    if flag(dev)
-        ghc-options:
-            -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns
-            -Wnoncanonical-monad-instances
-
 test-suite tests
     type:             exitcode-stdio-1.0
     main-is:          Main.hs
     hs-source-dirs:   tests
-    default-language: Haskell2010
+    default-language: GHC2021
     build-depends:
-        base >=4.13 && <5.0,
-        directory >=1.3.2.0 && <1.4,
+        base >=4.15 && <5,
         exceptions >=0.8 && <0.11,
-        hspec >=2.0 && <3.0,
-        filepath >=1.2 && <1.5,
-        path >=0.6 && <0.9,
+        hspec >=2 && <3,
+        path >=0.7.1 && <0.10,
         path-io,
-        transformers >=0.3 && <0.6,
         unix-compat
 
     if flag(dev)
-        ghc-options: -Wall -Werror
+        ghc-options:
+            -Wall -Werror -Wredundant-constraints -Wpartial-fields
+            -Wunused-packages
 
     else
         ghc-options: -O2 -Wall
+
+    if impl(ghc >=9.8)
+        ghc-options: -Wno-x-partial
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -16,6 +16,9 @@
 
 main :: IO ()
 main = hspec . around withSandbox $ do
+
+{- ORMOLU_DISABLE -}
+
 #ifndef mingw32_HOST_OS
   beforeWith populatedDir $ do
     -- NOTE These tests fail on Windows as unix-compat does not implement
@@ -37,7 +40,6 @@
   describe "setCurrentDir" setCurrentDirSpec
   describe "withCurrentDir" withCurrentDirSpec
   describe "walkDirRel" walkDirRelSpec
-
 #ifndef mingw32_HOST_OS
   -- NOTE We can't quite test this on Windows as well, because the
   -- environmental variables HOME and TMPDIR do not exist there.
@@ -48,6 +50,8 @@
   describe "getXdgDir Cache"  getXdgCacheDirSpec
 #endif
 
+{- ORMOLU_ENABLE -}
+
 listDirSpec :: SpecWith (Path Abs Dir)
 listDirSpec = it "lists directory" $ \dir ->
   getDirStructure listDir dir `shouldReturn` populatedDirTop
@@ -162,7 +166,7 @@
 
 -- | Follows symbolic links.
 listDirRecurCyclic ::
-  (MonadIO m, MonadThrow m) =>
+  (MonadIO m) =>
   -- | Directory to list
   Path b Dir ->
   -- | Sub-directories and files
@@ -270,12 +274,21 @@
       pdir = root </> $(mkRelDir "pdir")
       withinSandbox = (pdir </>)
   ensureDir pdir
-  ensureDir $ withinSandbox $(mkRelDir "b")
+  let b = withinSandbox $(mkRelDir "b")
+  ensureDir b
   ensureDir $ withinSandbox $(mkRelDir "b/c")
-  -- to verify that we do not follow symbolic links. We should not list b's
-  -- tree under 'a'.
+  -- Create a read-only directory with a file inside to test that the code
+  -- that copies directory permissions can handle that gracefully.
+  --
+  -- See: https://github.com/mrkkrp/path-io/pull/82
+  let readonlyDir = withinSandbox $(mkRelDir "readonly-dir")
+  ensureDir readonlyDir
+  -- We should not list b's tree under 'a' in order to verify that we do not
+  -- follow symbolic links.
   createSymbolicLink "b" (toFilePath $ withinSandbox $(mkRelFile "a"))
   forM_ files $ (`writeFile` "") . toFilePath . withinSandbox
+  getPermissions readonlyDir
+    >>= setPermissions readonlyDir . setOwnerWritable False
   return pdir
 
 -- | Get the inner structure of a directory. Items are sorted, so it's
@@ -308,11 +321,13 @@
 populatedDirStructure =
   ( [ $(mkRelDir "a"),
       $(mkRelDir "b"),
-      $(mkRelDir "b/c")
+      $(mkRelDir "b/c"),
+      $(mkRelDir "readonly-dir")
     ],
     [ $(mkRelFile "b/c/three.txt"),
       $(mkRelFile "b/two.txt"),
-      $(mkRelFile "one.txt")
+      $(mkRelFile "one.txt"),
+      $(mkRelFile "readonly-dir/two.txt")
     ]
   )
 
@@ -355,26 +370,28 @@
   createSymbolicLink "../../e" (toFilePath $ withinSandbox $(mkRelFile "e/f/g"))
   return pdir
 
--- | Top-level structure of populated directory as it should be scanned by
--- the 'listDir' function.
+-- | Top-level structure of the populated directory as it should be scanned
+-- by the 'listDir' function.
 populatedDirTop :: ([Path Rel Dir], [Path Rel File])
 populatedDirTop =
   ( [ $(mkRelDir "a"),
-      $(mkRelDir "b")
+      $(mkRelDir "b"),
+      $(mkRelDir "readonly-dir")
     ],
     [ $(mkRelFile "one.txt")
     ]
   )
 
--- | Structure of populated directory as it should be scanned by
--- 'listDirRecurWith' function using predicates to filter out dir 'c' and the
--- file 'two.txt'
+-- | Structure of the populated directory as it should be scanned by
+-- 'listDirRecurWith' function using predicates to filter out dir 'c' and
+-- the file @two.txt@.
 populatedDirRecurWith :: ([Path Rel Dir], [Path Rel File])
 populatedDirRecurWith =
   ( [ $(mkRelDir "a"),
-      $(mkRelDir "b")
+      $(mkRelDir "b"),
+      $(mkRelDir "readonly-dir")
     ],
-    [ $(mkRelFile "a/c/three.txt"), -- via symbolic link
+    [ $(mkRelFile "a/c/three.txt"), -- via a symbolic link
       $(mkRelFile "b/c/three.txt"),
       $(mkRelFile "one.txt")
     ]
