diff --git a/lib/Filesystem.hs b/lib/Filesystem.hs
--- a/lib/Filesystem.hs
+++ b/lib/Filesystem.hs
@@ -861,19 +861,12 @@
 	(\h -> T.hPutStr h text)
 
 #ifdef SYSTEMFILEIO_LOCAL_OPEN_FILE
+-- | Copied from GHC.IO.FD.openFile
 openFile' :: String -> FilePath -> IO.IOMode -> (Maybe IO.TextEncoding) -> IO IO.Handle
 openFile' loc path mode codec = open where
-	mode_flags = case mode of
-		IO.ReadMode -> System.Posix.Internals.o_RDONLY
-		IO.WriteMode -> System.Posix.Internals.o_WRONLY
-		IO.ReadWriteMode -> System.Posix.Internals.o_RDWR
-		IO.AppendMode -> System.Posix.Internals.o_APPEND
-	flags = mode_flags .|.
-	        System.Posix.Internals.o_NOCTTY .|.
-	        System.Posix.Internals.o_NONBLOCK
-	
 	sys_c_open = System.Posix.Internals.c_open
 	sys_c_close = System.Posix.Internals.c_close
+	flags = iomodeFlags mode
 	open = withFilePath path $ \cPath -> do
 		c_fd <- throwErrnoPathIfMinus1Retry loc path (sys_c_open cPath flags 0o666)
 		(fd, fd_type) <- Exc.onException
@@ -884,6 +877,27 @@
 		Exc.onException
 			(mkHandleFromFD fd fd_type (encodeString path) mode False codec)
 			(GHC.IO.Device.close fd)
+
+iomodeFlags :: IO.IOMode -> CInt
+iomodeFlags mode = cased .|. commonFlags where
+	cased = case mode of
+		IO.ReadMode -> flagsR
+#ifdef mingw32_HOST_OS
+		IO.WriteMode -> flagsW .|. System.Posix.Internals.o_TRUNC
+#else
+		IO.WriteMode -> flagsW
+#endif
+		IO.ReadWriteMode -> flagsRW
+		IO.AppendMode -> flagsA
+	
+	flagsR  = System.Posix.Internals.o_RDONLY
+	flagsW  = outputFlags .|. System.Posix.Internals.o_WRONLY
+	flagsRW = outputFlags .|. System.Posix.Internals.o_RDWR
+	flagsA  = flagsW      .|. System.Posix.Internals.o_APPEND
+	
+	commonFlags = System.Posix.Internals.o_NOCTTY .|.
+	              System.Posix.Internals.o_NONBLOCK
+	outputFlags = System.Posix.Internals.o_CREAT
 
 #endif
 
diff --git a/system-fileio.cabal b/system-fileio.cabal
--- a/system-fileio.cabal
+++ b/system-fileio.cabal
@@ -1,5 +1,5 @@
 name: system-fileio
-version: 0.2.5
+version: 0.2.6
 license: MIT
 license-file: license.txt
 author: John Millikin <jmillikin@gmail.com>
@@ -42,7 +42,7 @@
 source-repository this
   type: bazaar
   location: https://john-millikin.com/branches/system-fileio/0.2/
-  tag: system-fileio_0.2.5
+  tag: system-fileio_0.2.6
 
 library
   ghc-options: -Wall -O2
diff --git a/tests/FilesystemTests/Posix.hs b/tests/FilesystemTests/Posix.hs
--- a/tests/FilesystemTests/Posix.hs
+++ b/tests/FilesystemTests/Posix.hs
@@ -184,15 +184,35 @@
 		, test_GetSize "iso8859"
 			(decode "\xA1\xA2\xA3.txt")
 		]
-	, todo "copyFile"
+	, suite "copyFile"
+		[ test_CopyFile "ascii"
+			(decode "old_test.txt")
+			(decode "new_test.txt")
+		, test_CopyFile "utf8"
+			(fromText "old_\xA1\xA2.txt")
+			(fromText "new_\xA1\xA2.txt")
+		, test_CopyFile "iso8859"
+			(decode "old_\xA1\xA2\xA3.txt")
+			(decode "new_\xA1\xA2\xA3.txt")
+		]
 	, todo "openFile"
 	, suite "withFile"
-		[ test_WithFile "ascii"
-			(decode "test.txt")
-		, test_WithFile "utf8"
-			(fromText "\xA1\xA2.txt")
-		, test_WithFile "iso8859"
-			(decode "\xA1\xA2\xA3.txt")
+		[ suite "read"
+			[ test_WithFile_Read "ascii"
+				(decode "test.txt")
+			, test_WithFile_Read "utf8"
+				(fromText "\xA1\xA2.txt")
+			, test_WithFile_Read "iso8859"
+				(decode "\xA1\xA2\xA3.txt")
+			]
+		, suite "write"
+			[ test_WithFile_Write "ascii"
+				(decode "test.txt")
+			, test_WithFile_Write "utf8"
+				(fromText "\xA1\xA2.txt")
+			, test_WithFile_Write "iso8859"
+				(decode "\xA1\xA2\xA3.txt")
+			]
 		]
 	, todo "readFile"
 	, todo "writeFile"
@@ -254,6 +274,32 @@
 	$expect (not old_after)
 	$expect new_after
 
+test_CopyFile :: Text -> FilePath -> FilePath -> Suite
+test_CopyFile test_name old_name new_name = assertionsWithTemp test_name $ \tmp -> do
+	let old_path = tmp </> old_name
+	let new_path = tmp </> new_name
+	
+	touch_ffi old_path ""
+	
+	old_before <- liftIO $ Filesystem.isFile old_path
+	new_before <- liftIO $ Filesystem.isFile new_path
+	$expect old_before
+	$expect (not new_before)
+	
+	liftIO $ Filesystem.copyFile old_path new_path
+	
+	old_after <- liftIO $ Filesystem.isFile old_path
+	new_after <- liftIO $ Filesystem.isFile new_path
+	$expect old_after
+	$expect new_after
+	old_contents <- liftIO $
+		Filesystem.withTextFile old_path ReadMode $
+		Data.Text.IO.hGetContents
+	new_contents <- liftIO $
+		Filesystem.withTextFile new_path ReadMode $
+		Data.Text.IO.hGetContents
+	$expect (equalLines old_contents new_contents)
+
 test_CanonicalizePath :: Text -> FilePath -> FilePath -> Suite
 test_CanonicalizePath test_name src_name dst_name = assertionsWithTemp test_name $ \tmp -> do
 	let src_path = tmp </> src_name
@@ -466,12 +512,26 @@
 	size <- liftIO $ Filesystem.getSize file_path
 	$expect (equal size (toInteger (Data.ByteString.length contents)))
 
-test_WithFile :: Text -> FilePath -> Suite
-test_WithFile test_name file_name = assertionsWithTemp test_name $ \tmp -> do
+test_WithFile_Read :: Text -> FilePath -> Suite
+test_WithFile_Read test_name file_name = assertionsWithTemp test_name $ \tmp -> do
 	let file_path = tmp </> file_name
 	let contents = "contents\n"
 	
 	touch_ffi file_path contents
+	
+	read_contents <- liftIO $
+		Filesystem.withFile file_path ReadMode $
+		Data.ByteString.hGetContents
+	$expect (equalLines contents read_contents)
+
+test_WithFile_Write :: Text -> FilePath -> Suite
+test_WithFile_Write test_name file_name = assertionsWithTemp test_name $ \tmp -> do
+	let file_path = tmp </> file_name
+	let contents = "contents\n"
+	
+	liftIO $
+		Filesystem.withFile file_path WriteMode $
+		(\h -> Data.ByteString.hPut h contents)
 	
 	read_contents <- liftIO $
 		Filesystem.withFile file_path ReadMode $
