diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,32 @@
 # Revision history for fs-api
 
+## ?.?.?.? -- ????-??-??
+
+## 0.4.0.0 -- 2025-05-30
+
+### Breaking
+
+* Add a new `MustExist` option to `AllowExisting`.
+
+### Non-breaking
+
+* Make the orphan `Condense` instance for `System.IO.SeekMode` into a non-orphan
+  instance. The instance is still exported from the same modules it was exported
+  from previously, `System.FS.API.Types` in particular.
+
+### Patch
+
+* Make it build with `ghc-9.12`.
+* Support `io-classes-1.8.0.1`.
+* Drop support for `ghc-8.10`.
+* Bugfix: opening a file in read mode now expects the file to exist already.
+  This was already the semantics when using `hOpen` from the `ioHasFS` instance,
+  but it was not reflected in the `allowExisting` function. `allowExisting
+  Readmode` now returns `MustExist` instead of `AllowExisting`.
+* Bugfix: `hGetBufExactly` and `hGetBufExactlyAt` would previously try to read
+  too many bytes in the presence of partial reads. These functions now properly
+  count the number of remaining bytes that have to be read.
+
 ## 0.3.0.1 -- 2024-10-02
 
 ### Patch
diff --git a/fs-api.cabal b/fs-api.cabal
--- a/fs-api.cabal
+++ b/fs-api.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.0
 name:            fs-api
-version:         0.3.0.1
+version:         0.4.0.0
 synopsis:        Abstract interface for the file system
 description:     Abstract interface for the file system.
 license:         Apache-2.0
@@ -19,13 +19,19 @@
   CHANGELOG.md
   README.md
 
-tested-with:     GHC ==8.10 || ==9.2 || ==9.4 || ==9.6 || ==9.8 || ==9.10
+tested-with:     GHC ==9.2 || ==9.4 || ==9.6 || ==9.8 || ==9.10 || ==9.12
 
 source-repository head
   type:     git
   location: https://github.com/input-output-hk/fs-sim
   subdir:   fs-api
 
+source-repository this
+  type:     git
+  location: https://github.com/input-output-hk/fs-sim
+  subdir:   fs-api
+  tag:      fs-api-0.4.0.0
+
 library
   hs-source-dirs:   src
   exposed-modules:
@@ -41,14 +47,14 @@
 
   default-language: Haskell2010
   build-depends:
-    , base             >=4.14  && <4.21
+    , base             >=4.16  && <4.22
     , bytestring       ^>=0.10 || ^>=0.11 || ^>=0.12
     , containers       ^>=0.5  || ^>=0.6  || ^>=0.7
     , deepseq          ^>=1.4  || ^>=1.5
     , digest           ^>=0.0
     , directory        ^>=1.3
     , filepath         ^>=1.4  || ^>=1.5
-    , io-classes       ^>=1.6  || ^>=1.7
+    , io-classes       ^>=1.6  || ^>=1.7  || ^>=1.8.0.1
     , primitive        ^>=0.9
     , safe-wild-cards  ^>=1.0
     , text             ^>=1.2  || ^>=2.0  || ^>=2.1
@@ -56,7 +62,7 @@
   if os(windows)
     hs-source-dirs:  src-win32
     exposed-modules: System.FS.IO.Windows
-    build-depends:   Win32 >=2.6.1.0
+    build-depends:   Win32 ^>=2.14
 
   -- every other distribution is handled like it is Unix-based
   else
diff --git a/src-unix/System/FS/IO/Unix.hs b/src-unix/System/FS/IO/Unix.hs
--- a/src-unix/System/FS/IO/Unix.hs
+++ b/src-unix/System/FS/IO/Unix.hs
@@ -73,19 +73,16 @@
       AppendMode    ex -> ( Posix.WriteOnly
                           , defaultFileFlags { Posix.append = True
                                              , Posix.exclusive = isExcl ex
-                                             , Posix.creat = Just Posix.stdFileMode }
+                                             , Posix.creat = creat ex }
                           )
       ReadWriteMode ex -> ( Posix.ReadWrite
                           , defaultFileFlags { Posix.exclusive = isExcl ex
-                                             , Posix.creat = Just Posix.stdFileMode }
+                                             , Posix.creat = creat ex }
                           )
       WriteMode     ex -> ( Posix.ReadWrite
                           , defaultFileFlags { Posix.exclusive = isExcl ex
-                                             , Posix.creat = Just Posix.stdFileMode }
+                                             , Posix.creat = creat ex }
                           )
-
-    isExcl AllowExisting = False
-    isExcl MustBeNew     = True
 # else
 open fp openMode = Posix.openFd fp posixOpenMode fileMode fileFlags
   where
@@ -95,22 +92,26 @@
                           , defaultFileFlags
                           )
       AppendMode    ex -> ( Posix.WriteOnly
-                          , Just Posix.stdFileMode
+                          , creat ex
                           , defaultFileFlags { Posix.append = True
                                              , Posix.exclusive = isExcl ex }
                           )
       ReadWriteMode ex -> ( Posix.ReadWrite
-                          , Just Posix.stdFileMode
+                          , creat ex
                           , defaultFileFlags { Posix.exclusive = isExcl ex }
                           )
       WriteMode     ex -> ( Posix.ReadWrite
-                          , Just Posix.stdFileMode
+                          , creat ex
                           , defaultFileFlags { Posix.exclusive = isExcl ex }
                           )
-
+# endif
     isExcl AllowExisting = False
     isExcl MustBeNew     = True
-# endif
+    isExcl MustExist     = False
+
+    creat AllowExisting = Just Posix.stdFileMode
+    creat MustBeNew     = Just Posix.stdFileMode
+    creat MustExist     = Nothing
 
 -- | Writes the data pointed by the input 'Ptr Word8' into the input 'FHandle'.
 write :: FHandle -> Ptr Word8 -> Int64 -> IO Word32
diff --git a/src-win32/System/FS/IO/Windows.hs b/src-win32/System/FS/IO/Windows.hs
--- a/src-win32/System/FS/IO/Windows.hs
+++ b/src-win32/System/FS/IO/Windows.hs
@@ -60,6 +60,7 @@
       ReadWriteMode ex -> (gENERIC_READ .|. gENERIC_WRITE, createNew ex)
     createNew AllowExisting = oPEN_ALWAYS
     createNew MustBeNew     = cREATE_NEW
+    createNew MustExist     = oPEN_EXISTING
 
 write :: FHandle -> Ptr Word8 -> Int64 -> IO Word32
 write fh data' bytes = withOpenHandle "write" fh $ \h ->
diff --git a/src/System/FS/API.hs b/src/System/FS/API.hs
--- a/src/System/FS/API.hs
+++ b/src/System/FS/API.hs
@@ -263,7 +263,7 @@
     go !remainingCount !currentBufOff
       | remainingCount == 0 = pure c
       | otherwise            = do
-          readBytes <- hGetBufSome hfs h buf currentBufOff c
+          readBytes <- hGetBufSome hfs h buf currentBufOff remainingCount
           if readBytes == 0 then
             throwIO FsError {
                 fsErrorType   = FsReachedEOF
@@ -294,7 +294,7 @@
     go !remainingCount !currentOffset !currentBufOff
       | remainingCount == 0 = pure c
       | otherwise            = do
-          readBytes <- hGetBufSomeAt hfs h buf currentBufOff c currentOffset
+          readBytes <- hGetBufSomeAt hfs h buf currentBufOff remainingCount currentOffset
           if readBytes == 0 then
             throwIO FsError {
                 fsErrorType   = FsReachedEOF
diff --git a/src/System/FS/API/Types.hs b/src/System/FS/API/Types.hs
--- a/src/System/FS/API/Types.hs
+++ b/src/System/FS/API/Types.hs
@@ -5,8 +5,6 @@
 {-# LANGUAGE RecordWildCards            #-}
 {-# LANGUAGE ScopedTypeVariables        #-}
 
--- For Show Errno and Condense SeekMode instances
-{-# OPTIONS_GHC -Wno-orphans #-}
 module System.FS.API.Types (
     -- * Modes
     AllowExisting (..)
@@ -72,6 +70,15 @@
 -------------------------------------------------------------------------------}
 
 -- | How to 'System.FS.API.hOpen' a new file.
+--
+-- Each mode of file operation has an associated 'AllowExisting' parameter which
+-- specifies the semantics of how to handle the existence or non-existence of
+-- the file.
+--
+-- /Notably however/, opening a file in read mode with the @ReadMode@ value
+-- /implicitly/ has the associated 'AllowExisting' value of 'MustExist'. This is
+-- because opening a non-existing file in 'ReadMode' provides access to exactly
+-- 0 bytes of data and is hence a useless operation.
 data OpenMode
   = ReadMode
   | WriteMode     AllowExisting
@@ -85,13 +92,19 @@
     -- ^ The file may already exist. If it does, it is reopened. If it
     -- doesn't, it is created.
   | MustBeNew
-    -- ^ The file may not yet exist. If it does, an error
+    -- ^ The file must not yet exist. If it does, an error
     -- ('FsResourceAlreadyExist') is thrown.
+  | MustExist
+    -- ^ The file must already exist. If it does not, an error
+    -- ('FsResourceDoesNotExist') is thrown.
+    --
+    -- /Note:/ If opening a file in 'ReadMode', then the file must exist
+    -- or an exception is thrown.
   deriving (Eq, Show)
 
 allowExisting :: OpenMode -> AllowExisting
 allowExisting openMode = case openMode of
-  ReadMode         -> AllowExisting
+  ReadMode         -> MustExist
   WriteMode     ex -> ex
   AppendMode    ex -> ex
   ReadWriteMode ex -> ex
@@ -452,14 +465,10 @@
   Condense instances
 -------------------------------------------------------------------------------}
 
-instance Condense SeekMode where
-  condense RelativeSeek = "r"
-  condense AbsoluteSeek = "a"
-  condense SeekFromEnd  = "e"
-
 instance Condense AllowExisting where
   condense AllowExisting = ""
   condense MustBeNew     = "!"
+  condense MustExist     = "+"
 
 instance Condense OpenMode where
     condense ReadMode           = "r"
diff --git a/src/System/FS/Condense.hs b/src/System/FS/Condense.hs
--- a/src/System/FS/Condense.hs
+++ b/src/System/FS/Condense.hs
@@ -23,6 +23,7 @@
 import           Data.Void
 import           Data.Word
 import           Numeric.Natural
+import           System.IO (SeekMode (..))
 import           Text.Printf (printf)
 
 {-------------------------------------------------------------------------------
@@ -111,3 +112,8 @@
 
 instance Condense BS.Lazy.ByteString where
   condense bs = show bs ++ "<" ++ show (BS.Lazy.length bs) ++ "b>"
+
+instance Condense SeekMode where
+  condense RelativeSeek = "r"
+  condense AbsoluteSeek = "a"
+  condense SeekFromEnd  = "e"
