Skip to content

Commit bac668b

Browse files
authored
Merge pull request #97 from input-output-hk/jdral/must-exist
Adding `MustExist` constructor to assert a file is assumed to exist
2 parents 12dae42 + 352e118 commit bac668b

File tree

8 files changed

+132
-37
lines changed

8 files changed

+132
-37
lines changed

fs-api/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## ?.?.?.? -- ????-??-??
44

5+
### Breaking
6+
7+
* Add a new `MustExist` option to `AllowExisting`.
8+
59
### Non-breaking
610

711
* Make the orphan `Condense` instance for `System.IO.SeekMode` into a non-orphan
@@ -11,6 +15,10 @@
1115
### Patch
1216

1317
* Make it build with `ghc-9.12`.
18+
* Bugfix: opening a file in read mode now expects the file to exist already.
19+
This was already the semantics when using `hOpen` from the `ioHasFS` instance,
20+
but it was not reflected in the `allowExisting` function. `allowExisting
21+
Readmode` now returns `MustExist` instead of `AllowExisting`.
1422

1523
## 0.3.0.1 -- 2024-10-02
1624

fs-api/src-unix/System/FS/IO/Unix.hs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,16 @@ open fp openMode = Posix.openFd fp posixOpenMode fileFlags
7373
AppendMode ex -> ( Posix.WriteOnly
7474
, defaultFileFlags { Posix.append = True
7575
, Posix.exclusive = isExcl ex
76-
, Posix.creat = Just Posix.stdFileMode }
76+
, Posix.creat = creat ex }
7777
)
7878
ReadWriteMode ex -> ( Posix.ReadWrite
7979
, defaultFileFlags { Posix.exclusive = isExcl ex
80-
, Posix.creat = Just Posix.stdFileMode }
80+
, Posix.creat = creat ex }
8181
)
8282
WriteMode ex -> ( Posix.ReadWrite
8383
, defaultFileFlags { Posix.exclusive = isExcl ex
84-
, Posix.creat = Just Posix.stdFileMode }
84+
, Posix.creat = creat ex }
8585
)
86-
87-
isExcl AllowExisting = False
88-
isExcl MustBeNew = True
8986
# else
9087
open fp openMode = Posix.openFd fp posixOpenMode fileMode fileFlags
9188
where
@@ -95,22 +92,26 @@ open fp openMode = Posix.openFd fp posixOpenMode fileMode fileFlags
9592
, defaultFileFlags
9693
)
9794
AppendMode ex -> ( Posix.WriteOnly
98-
, Just Posix.stdFileMode
95+
, creat ex
9996
, defaultFileFlags { Posix.append = True
10097
, Posix.exclusive = isExcl ex }
10198
)
10299
ReadWriteMode ex -> ( Posix.ReadWrite
103-
, Just Posix.stdFileMode
100+
, creat ex
104101
, defaultFileFlags { Posix.exclusive = isExcl ex }
105102
)
106103
WriteMode ex -> ( Posix.ReadWrite
107-
, Just Posix.stdFileMode
104+
, creat ex
108105
, defaultFileFlags { Posix.exclusive = isExcl ex }
109106
)
110-
107+
# endif
111108
isExcl AllowExisting = False
112109
isExcl MustBeNew = True
113-
# endif
110+
isExcl MustExist = False
111+
112+
creat AllowExisting = Just Posix.stdFileMode
113+
creat MustBeNew = Just Posix.stdFileMode
114+
creat MustExist = Nothing
114115

115116
-- | Writes the data pointed by the input 'Ptr Word8' into the input 'FHandle'.
116117
write :: FHandle -> Ptr Word8 -> Int64 -> IO Word32

fs-api/src-win32/System/FS/IO/Windows.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ open filename openMode = do
6060
ReadWriteMode ex -> (gENERIC_READ .|. gENERIC_WRITE, createNew ex)
6161
createNew AllowExisting = oPEN_ALWAYS
6262
createNew MustBeNew = cREATE_NEW
63+
createNew MustExist = oPEN_EXISTING
6364

6465
write :: FHandle -> Ptr Word8 -> Int64 -> IO Word32
6566
write fh data' bytes = withOpenHandle "write" fh $ \h ->

fs-api/src/System/FS/API/Types.hs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ import System.FS.Condense
7070
-------------------------------------------------------------------------------}
7171

7272
-- | How to 'System.FS.API.hOpen' a new file.
73+
--
74+
-- Each mode of file operation has an associated 'AllowExisting' parameter which
75+
-- specifies the semantics of how to handle the existence or non-existence of
76+
-- the file.
77+
--
78+
-- /Notably however/, opening a file in read mode with the @ReadMode@ value
79+
-- /implicitly/ has the associated 'AllowExisting' value of 'MustExist'. This is
80+
-- because opening a non-existing file in 'ReadMode' provides access to exactly
81+
-- 0 bytes of data and is hence a useless operation.
7382
data OpenMode
7483
= ReadMode
7584
| WriteMode AllowExisting
@@ -83,13 +92,19 @@ data AllowExisting
8392
-- ^ The file may already exist. If it does, it is reopened. If it
8493
-- doesn't, it is created.
8594
| MustBeNew
86-
-- ^ The file may not yet exist. If it does, an error
95+
-- ^ The file must not yet exist. If it does, an error
8796
-- ('FsResourceAlreadyExist') is thrown.
97+
| MustExist
98+
-- ^ The file must already exist. If it does not, an error
99+
-- ('FsResourceDoesNotExist') is thrown.
100+
--
101+
-- /Note:/ If opening a file in 'ReadMode', then the file must exist
102+
-- or an exception is thrown.
88103
deriving (Eq, Show)
89104

90105
allowExisting :: OpenMode -> AllowExisting
91106
allowExisting openMode = case openMode of
92-
ReadMode -> AllowExisting
107+
ReadMode -> MustExist
93108
WriteMode ex -> ex
94109
AppendMode ex -> ex
95110
ReadWriteMode ex -> ex
@@ -453,6 +468,7 @@ ioToFsErrorType ioErr = case Errno <$> GHC.ioe_errno ioErr of
453468
instance Condense AllowExisting where
454469
condense AllowExisting = ""
455470
condense MustBeNew = "!"
471+
condense MustExist = "+"
456472

457473
instance Condense OpenMode where
458474
condense ReadMode = "r"

fs-sim/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
### Patch
3333

3434
* Make it build with `ghc-9.12`.
35+
* Support the new `MustExist` option for `AllowExisting` that was added in
36+
`fs-api`.
3537

3638
## 0.3.1.0 -- 2024-12-10
3739

fs-sim/src/System/FS/Sim/FsTree.hs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,14 +231,23 @@ getDir fp =
231231
Specific file system functions
232232
-------------------------------------------------------------------------------}
233233

234-
-- | Open a file: create it if necessary or throw an error if it existed
235-
-- already wile we were supposed to create it from scratch (when passed
236-
-- 'MustBeNew').
234+
-- | Open a file: create it if necessary or throw an error if either:
235+
-- 1. It existed already while we were supposed to create it from scratch
236+
-- (when passed 'MustBeNew').
237+
-- 2. It did not already exists when we expected to (when passed 'MustExist').
237238
openFile :: Monoid a
238239
=> FsPath -> AllowExisting -> FsTree a -> Either FsTreeError (FsTree a)
239-
openFile fp ex = alterFile fp Left (Right mempty) $ \a -> case ex of
240-
AllowExisting -> Right a
241-
MustBeNew -> Left (FsExists fp)
240+
openFile fp ex = alterFile fp Left caseDoesNotExist caseAlreadyExist
241+
where
242+
caseAlreadyExist a = case ex of
243+
AllowExisting -> Right a
244+
MustBeNew -> Left (FsExists fp)
245+
MustExist -> Right a
246+
247+
caseDoesNotExist = case ex of
248+
AllowExisting -> Right mempty
249+
MustBeNew -> Right mempty
250+
MustExist -> Left (FsMissing fp (pathLast fp :| []))
242251

243252
-- | Replace the contents of the specified file (which must exist)
244253
replace :: FsPath -> a -> FsTree a -> Either FsTreeError (FsTree a)

fs-sim/src/System/FS/Sim/MockFS.hs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ module System.FS.Sim.MockFS (
6565
, hPutBufSomeAt
6666
) where
6767

68-
import Control.Monad (forM, forM_, unless, void, when)
68+
import Control.Monad (forM, forM_, unless, when)
6969
import Control.Monad.Except (MonadError, throwError)
7070
import Control.Monad.Primitive (PrimMonad (..))
7171
import Control.Monad.State.Strict (MonadState, get, gets, put)
@@ -491,8 +491,6 @@ hOpen fp openMode = do
491491
, fsErrorStack = prettyCallStack
492492
, fsLimitation = True
493493
}
494-
when (openMode == ReadMode) $ void $
495-
checkFsTree $ FS.getFile fp (mockFiles fs)
496494
files' <- checkFsTree $ FS.openFile fp ex (mockFiles fs)
497495
return $ newHandle (fs { mockFiles = files' })
498496
(OpenHandle fp (filePtr openMode))

fs-sim/test/Test/System/FS/StateMachine.hs

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ generator Model{..} = oneof $ concat [
676676
(rf, wf) = if fileExists then (10,3) else (1,3)
677677

678678
genAllowExisting :: Gen AllowExisting
679-
genAllowExisting = elements [AllowExisting, MustBeNew]
679+
genAllowExisting = elements [AllowExisting, MustBeNew, MustExist]
680680

681681
genSeekMode :: Gen SeekMode
682682
genSeekMode = elements [
@@ -1004,84 +1004,107 @@ data Tag =
10041004
-- > Get ..
10051005
| TagPutTruncateGet
10061006

1007-
-- Close a handle 2 times
1007+
-- | Close a handle 2 times
10081008
--
10091009
-- > h <- Open ..
10101010
-- > close h
10111011
-- > close h
10121012
| TagClosedTwice
10131013

1014-
-- Open an existing file with ReadMode and then with WriteMode
1014+
-- | Open an existing file with ReadMode and then with WriteMode
10151015
--
10161016
-- > open fp ReadMode
10171017
-- > open fp Write
10181018
| TagOpenReadThenWrite
10191019

1020-
-- Open 2 Readers of a file.
1020+
-- | Open 2 Readers of a file.
10211021
--
10221022
-- > open fp ReadMode
10231023
-- > open fp ReadMode
10241024
| TagOpenReadThenRead
10251025

1026-
-- ListDir on a non empty dirextory.
1026+
-- | ListDir on a non empty dirextory.
10271027
--
10281028
-- > CreateDirIfMissing True a/b
10291029
-- > ListDirectory a
10301030
| TagCreateDirWithParentsThenListDirNotNull
10311031

1032-
-- Read from an AppendMode file
1032+
-- | Read from an AppendMode file
10331033
--
10341034
-- > h <- Open fp AppendMode
10351035
-- > Read h ..
10361036
| TagReadInvalid
10371037

1038-
-- Write to a read only file
1038+
-- | Write to a read only file
10391039
--
10401040
-- > h <- Open fp ReadMode
10411041
-- > Put h ..
10421042
| TagWriteInvalid
10431043

1044-
-- Put Seek and Get
1044+
-- | Put Seek and Get
10451045
--
10461046
-- > Put ..
10471047
-- > Seek ..
10481048
-- > Get ..
10491049
| TagPutSeekGet
10501050

1051-
-- Put Seek (negative) and Get
1051+
-- | Put Seek (negative) and Get
10521052
--
10531053
-- > Put ..
10541054
-- > Seek .. (negative)
10551055
-- > Get ..
10561056
| TagPutSeekNegGet
10571057

1058-
-- Open with MustBeNew (O_EXCL flag), but the file already existed.
1058+
-- | Open with MustBeNew (O_EXCL flag), but the file already existed.
10591059
--
10601060
-- > h <- Open fp (AppendMode _)
10611061
-- > Close h
10621062
-- > Open fp (AppendMode MustBeNew)
10631063
| TagExclusiveFail
10641064

1065+
-- | Open a file in read mode successfully
1066+
--
1067+
-- > h <- Open fp (WriteMode _)
1068+
-- > Close h
1069+
-- > h <- Open fp ReadMode
1070+
| TagReadModeMustExist
1071+
1072+
-- | Open a file in read mode, but it fails because the file does not exist.
1073+
--
1074+
-- > h <- Open fp ReadMode
1075+
| TagReadModeMustExistFail
1076+
1077+
-- | Open a file in non-read mode with 'MustExist' successfully.
1078+
--
1079+
-- > h <- Open fp (_ MustBeNew)
1080+
-- > Close h
1081+
-- > h <- Open fp (_ MustExist)
1082+
| TagFileMustExist
1083+
1084+
-- | Open a file in non-read mode with 'MustExist', but it fails because the
1085+
-- files does not exist.
1086+
--
1087+
-- > h <- Open fp (_ MustExist)
1088+
| TagFileMustExistFail
10651089

1066-
-- Reading returns an empty bytestring when EOF
1090+
-- | Reading returns an empty bytestring when EOF
10671091
--
10681092
-- > h <- open fp ReadMode
10691093
-- > Get h 1 == ""
10701094
| TagReadEOF
10711095

1072-
1073-
-- GetAt
1096+
-- | GetAt
10741097
--
10751098
-- > GetAt ...
10761099
| TagPread
10771100

1078-
-- Roundtrip for I/O with user-supplied buffers
1101+
-- | Roundtrip for I/O with user-supplied buffers
10791102
--
10801103
-- > PutBuf h bs c
10811104
-- > GetBuf h c (==bs)
10821105
| TagPutGetBuf
10831106

1084-
-- Roundtrip for I/O with user-supplied buffers
1107+
-- | Roundtrip for I/O with user-supplied buffers
10851108
--
10861109
-- > PutBufAt h bs c o
10871110
-- > GetBufAt h c o (==bs)
@@ -1136,6 +1159,10 @@ tag = C.classify [
11361159
, tagPutSeekGet Set.empty Set.empty
11371160
, tagPutSeekNegGet Set.empty Set.empty
11381161
, tagExclusiveFail
1162+
, tagReadModeMustExist
1163+
, tagReadModeMustExistFail
1164+
, tagFileMustExist
1165+
, tagFileMustExistFail
11391166
, tagReadEOF
11401167
, tagPread
11411168
, tagPutGetBuf Set.empty
@@ -1481,6 +1508,39 @@ tag = C.classify [
14811508
Left TagExclusiveFail
14821509
_otherwise -> Right tagExclusiveFail
14831510

1511+
tagReadModeMustExist :: EventPred
1512+
tagReadModeMustExist = C.predicate $ \ev ->
1513+
case (eventMockCmd ev, eventMockResp ev) of
1514+
(Open _ ReadMode, Resp (Right (RHandle _))) -> Left TagReadModeMustExist
1515+
_otherwise -> Right tagReadModeMustExist
1516+
1517+
tagReadModeMustExistFail :: EventPred
1518+
tagReadModeMustExistFail = C.predicate $ \ev ->
1519+
case (eventMockCmd ev, eventMockResp ev) of
1520+
(Open _ ReadMode, Resp (Left fsError))
1521+
| fsErrorType fsError == FsResourceDoesNotExist ->
1522+
Left TagReadModeMustExistFail
1523+
_otherwise -> Right tagReadModeMustExistFail
1524+
1525+
tagFileMustExist :: EventPred
1526+
tagFileMustExist = C.predicate $ \ev ->
1527+
case (eventMockCmd ev, eventMockResp ev) of
1528+
(Open _ mode, Resp (Right (WHandle _ _)))
1529+
| MustExist <- allowExisting mode
1530+
, mode /= ReadMode
1531+
-> Left TagFileMustExist
1532+
_otherwise -> Right tagFileMustExist
1533+
1534+
tagFileMustExistFail :: EventPred
1535+
tagFileMustExistFail = C.predicate $ \ev ->
1536+
case (eventMockCmd ev, eventMockResp ev) of
1537+
(Open _ mode, Resp (Left fsError))
1538+
| MustExist <- allowExisting mode
1539+
, mode /= ReadMode
1540+
, fsErrorType fsError == FsResourceDoesNotExist ->
1541+
Left TagFileMustExistFail
1542+
_otherwise -> Right tagFileMustExistFail
1543+
14841544
tagReadEOF :: EventPred
14851545
tagReadEOF = successful $ \ev suc ->
14861546
case (eventMockCmd ev, suc) of

0 commit comments

Comments
 (0)