Skip to content

Commit 11cc517

Browse files
committed
Add binary mode functions for String
1 parent 556f718 commit 11cc517

File tree

4 files changed

+214
-6
lines changed

4 files changed

+214
-6
lines changed

atomic-write.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ library
5656
, System.AtomicWrite.Writer.LazyByteString
5757
, System.AtomicWrite.Writer.LazyByteString.Binary
5858
, System.AtomicWrite.Writer.String
59+
, System.AtomicWrite.Writer.String.Binary
5960
, System.AtomicWrite.Writer.Text
6061
, System.AtomicWrite.Writer.LazyText
6162
, System.AtomicWrite.Writer.LazyText.Binary
@@ -86,6 +87,7 @@ test-suite atomic-write-test
8687
, System.AtomicWrite.Writer.LazyByteStringSpec
8788
, System.AtomicWrite.Writer.LazyByteString.BinarySpec
8889
, System.AtomicWrite.Writer.StringSpec
90+
, System.AtomicWrite.Writer.String.BinarySpec
8991
, System.AtomicWrite.Writer.TextSpec
9092
, System.AtomicWrite.Writer.LazyTextSpec
9193
, System.AtomicWrite.Writer.LazyText.BinarySpec
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
module System.AtomicWrite.Writer.String.BinarySpec (spec) where
2+
3+
import Test.Hspec (Spec, describe, it,
4+
shouldBe)
5+
6+
import System.AtomicWrite.Writer.String.Binary (atomicWriteFile, atomicWriteFileWithMode)
7+
8+
import System.FilePath (joinPath)
9+
import System.IO.Temp (withSystemTempDirectory)
10+
import System.PosixCompat.Files (fileMode,
11+
getFileStatus,
12+
setFileCreationMask,
13+
setFileMode)
14+
15+
16+
{-# ANN module "HLint: ignore Reduce duplication" #-}
17+
18+
spec :: Spec
19+
spec = do
20+
describe "atomicWriteFile" $ do
21+
it "writes contents to a file" $
22+
withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do
23+
24+
let path = joinPath [ tmpDir, "writeTest.tmp" ]
25+
26+
atomicWriteFile path "just testing"
27+
28+
contents <- readFile path
29+
30+
contents `shouldBe` "just testing"
31+
32+
33+
it "preserves the permissions of original file, regardless of umask" $
34+
withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do
35+
let filePath = joinPath [tmpDir, "testFile"]
36+
37+
writeFile filePath "initial contents"
38+
setFileMode filePath 0o100644
39+
40+
newStat <- getFileStatus filePath
41+
fileMode newStat `shouldBe` 0o100644
42+
43+
-- New files are created with 100600 perms.
44+
_ <- setFileCreationMask 0o100066
45+
46+
-- Create a new file once different mask is set and make sure that mask
47+
-- is applied.
48+
writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask"
49+
sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"]
50+
fileMode sanityCheckStat `shouldBe` 0o100600
51+
52+
-- Since we move, this makes the new file assume the filemask of 0600
53+
atomicWriteFile filePath "new contents"
54+
55+
resultStat <- getFileStatus filePath
56+
57+
-- reset mask to not break subsequent specs
58+
_ <- setFileCreationMask 0o100022
59+
60+
-- Fails when using atomic mv command unless apply perms on initial file
61+
fileMode resultStat `shouldBe` 0o100644
62+
63+
64+
it "creates a new file with permissions based on active umask" $
65+
withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do
66+
let
67+
filePath = joinPath [tmpDir, "testFile"]
68+
sampleFilePath = joinPath [tmpDir, "sampleFile"]
69+
70+
-- Set somewhat distinctive defaults for test
71+
_ <- setFileCreationMask 0o100171
72+
73+
-- We don't know what the default file permissions are, so create a
74+
-- file to sample them.
75+
writeFile sampleFilePath "I'm being written to sample permissions"
76+
77+
newStat <- getFileStatus sampleFilePath
78+
fileMode newStat `shouldBe` 0o100606
79+
80+
atomicWriteFile filePath "new contents"
81+
82+
resultStat <- getFileStatus filePath
83+
84+
-- reset mask to not break subsequent specs
85+
_ <- setFileCreationMask 0o100022
86+
87+
-- The default tempfile permissions are 0600, so this fails unless we
88+
-- make sure that the default umask is relied on for creation of the
89+
-- tempfile.
90+
fileMode resultStat `shouldBe` 0o100606
91+
describe "atomicWriteFileWithMode" $ do
92+
it "writes contents to a file" $
93+
withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do
94+
95+
let path = joinPath [ tmpDir, "writeTest.tmp" ]
96+
97+
atomicWriteFileWithMode 0o100777 path "just testing"
98+
99+
contents <- readFile path
100+
101+
contents `shouldBe` "just testing"
102+
103+
104+
it "changes the permissions of a previously created file, regardless of umask" $
105+
withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do
106+
let filePath = joinPath [tmpDir, "testFile"]
107+
108+
writeFile filePath "initial contents"
109+
setFileMode filePath 0o100644
110+
111+
newStat <- getFileStatus filePath
112+
fileMode newStat `shouldBe` 0o100644
113+
114+
-- New files are created with 100600 perms.
115+
_ <- setFileCreationMask 0o100066
116+
117+
-- Create a new file once different mask is set and make sure that mask
118+
-- is applied.
119+
writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask"
120+
sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"]
121+
fileMode sanityCheckStat `shouldBe` 0o100600
122+
123+
-- Since we move, this makes the new file assume the filemask of 0600
124+
atomicWriteFileWithMode 0o100655 filePath "new contents"
125+
126+
resultStat <- getFileStatus filePath
127+
128+
-- reset mask to not break subsequent specs
129+
_ <- setFileCreationMask 0o100022
130+
131+
-- Fails when using atomic mv command unless apply perms on initial file
132+
fileMode resultStat `shouldBe` 0o100655
133+
134+
135+
it "creates a new file with specified permissions" $
136+
withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do
137+
let
138+
filePath = joinPath [tmpDir, "testFile"]
139+
atomicWriteFileWithMode 0o100606 filePath "new contents"
140+
141+
resultStat <- getFileStatus filePath
142+
143+
fileMode resultStat `shouldBe` 0o100606

spec/System/AtomicWrite/Writer/StringSpec.hs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
module System.AtomicWrite.Writer.StringSpec (spec) where
22

3-
import Test.Hspec (it, describe, shouldBe, Spec)
3+
import Test.Hspec (Spec, describe, it, shouldBe)
44

5-
import System.AtomicWrite.Writer.String (atomicWriteFile, atomicWriteFileWithMode)
5+
import System.AtomicWrite.Writer.String (atomicWriteFile,
6+
atomicWriteFileWithMode)
67

7-
import System.IO.Temp (withSystemTempDirectory)
8-
import System.FilePath (joinPath)
9-
import System.PosixCompat.Files
10-
(setFileMode, setFileCreationMask, getFileStatus, fileMode)
8+
import System.FilePath (joinPath)
9+
import System.IO.Temp (withSystemTempDirectory)
10+
import System.PosixCompat.Files (fileMode, getFileStatus,
11+
setFileCreationMask,
12+
setFileMode)
1113

1214

1315
{-# ANN module "HLint: ignore Reduce duplication" #-}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
-- |
2+
-- Module : System.AtomicWrite.Writer.String.Binary
3+
-- Copyright : © 2015-2017 Stack Builders Inc.
4+
-- License : MIT
5+
--
6+
-- Maintainer : Stack Builders <hackage@stackbuilders.com>
7+
-- Stability : experimental
8+
-- Portability : portable
9+
--
10+
-- Provides functionality to dump the contents of a String
11+
-- to a file in binary mode.
12+
13+
module System.AtomicWrite.Writer.String.Binary (atomicWriteFile, atomicWithFile, atomicWriteFileWithMode, atomicWithFileAndMode) where
14+
15+
import System.AtomicWrite.Internal (closeAndRename, maybeSetFileMode,
16+
tempFileFor)
17+
18+
import System.IO (Handle, hPutStr, hSetBinaryMode)
19+
20+
import System.Posix.Types (FileMode)
21+
22+
-- | Creates or modifies a file atomically on POSIX-compliant
23+
-- systems while preserving permissions.
24+
atomicWriteFile ::
25+
FilePath -- ^ The path where the file will be updated or created
26+
-> String -- ^ The content to write to the file
27+
-> IO ()
28+
atomicWriteFile = (. flip hPutStr) . atomicWithFile
29+
30+
31+
-- | Creates or modifies a file atomically on
32+
-- POSIX-compliant systems and updates permissions
33+
atomicWriteFileWithMode ::
34+
FileMode -- ^ The mode to set the file to
35+
-> FilePath -- ^ The path where the file will be updated or created
36+
-> String -- ^ The content to write to the file
37+
-> IO ()
38+
atomicWriteFileWithMode mode = ( . flip hPutStr)
39+
. atomicWithFileAndMode mode
40+
41+
-- | A general version of 'atomicWriteFile'
42+
atomicWithFile :: FilePath -> (Handle -> IO ()) -> IO ()
43+
atomicWithFile = atomicWithFileAndMaybeMode Nothing
44+
45+
-- | A general version of 'atomicWriteFileWithMode'
46+
atomicWithFileAndMode :: FileMode
47+
-> FilePath
48+
-> (Handle -> IO ())
49+
-> IO ()
50+
atomicWithFileAndMode mode = atomicWithFileAndMaybeMode (Just mode)
51+
52+
-- | Helper function
53+
atomicWithFileAndMaybeMode :: Maybe FileMode
54+
-> FilePath
55+
-> (Handle -> IO ())
56+
-> IO ()
57+
atomicWithFileAndMaybeMode mmode path action =
58+
tempFileFor path >>= \(tmpPath, h) -> hSetBinaryMode h True
59+
>> action h
60+
>> closeAndRename h tmpPath path
61+
>> maybeSetFileMode path mmode

0 commit comments

Comments
 (0)