I'm writing an application in Haskell and would like to display a meaningful error message to the user if readFile
or writeFile
fails. I'm currently catching IOError
s with Control.Exception.tryJust
and converting them to human-readable text.
However, I'm having trouble figuring out which errors I should catch and how to extract information from them. For example, assuming "/bin" is a directory and "/bin/ls" is a file, readFile "/bin"
and readFile "/bin/ls/asdf"
both give "inappropriate type" but (in my opinion) they are different errors. In the case of the first one, I could recover by processing each file within the directory, whereas the second is more like a "does not exist" type of error.
In relation to the previous example, there doesn't seem to be a portable way of catching "inappropriate type" errors. Looking at GHC.IO.Exception, InappropriateType
is marked GHC-only so I can't just pattern match on ioeGetErrorType
. I could pattern match on ioeGetErrorString
but I'm not sure if those strings are always the same across different platforms, compilers, locales, etc.
In summary, my questions are:
readFile
/writeFile
?InappropriateType
?Update:
Based on @ErikR's answer I'm looking at the fields of GHC.IO.Exception.IOException
with the following Haskell program:
import Control.Exception (try)
import GHC.IO.Exception (IOException(..))
import qualified Data.ByteString as B
main :: IO ()
main = do
try (readFile "/nonexistent") >>= printException
try (writeFile "/dev/full" " ") >>= printException
try (readFile "/root") >>= printException
try (readFile "/bin") >>= printException
try (writeFile "/bin" "") >>= printException
try (readFile "/bin/ls/asdf") >>= printException
try (writeFile "/bin/ls/asdf" "") >>= printException
try (B.readFile "/dev/null") >>= printException
-- I have /media/backups mounted as read-only. Substitute your own read-only
-- filesystem for this one
try (writeFile "/media/backups/asdf" "") >>= printException
printException :: Either IOError a -> IO ()
printException (Right _) = putStrLn "No exception caught"
printException (Left e) = putStrLn $ concat [ "ioe_filename = "
, show $ ioe_filename e
, ", ioe_description = "
, show $ ioe_description e
, ", ioe_errno = "
, show $ ioe_errno e
]
The output on Debian Sid GNU/Linux with GHC 7.10.3 is:
ioe_filename = Just "/nonexistent", ioe_description = "No such file or directory", ioe_errno = Just 2
ioe_filename = Just "/dev/full", ioe_description = "No space left on device", ioe_errno = Just 28
ioe_filename = Just "/root", ioe_description = "Permission denied", ioe_errno = Just 13
ioe_filename = Just "/bin", ioe_description = "is a directory", ioe_errno = Nothing
ioe_filename = Just "/bin", ioe_description = "Is a directory", ioe_errno = Just 21
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/dev/null", ioe_description = "not a regular file", ioe_errno = Nothing
ioe_filename = Just "/media/backups/asdf", ioe_description = "Read-only file system", ioe_errno = Just 30
- Which exceptions should I be catching for readFile/writeFile?
Under OS X, if you use openFile
followed by hGetContents
instead of readFile
then you will get different exceptions for the cases you mention.
openFile "/bin/ls/asdf" ...
will throw a "no such file or directory" exception whereas openFile "/bin" ...
will throw "inappropriate type".
Under Linux both open calls will throw a "inappropriate type" exception. However, you can distinguish between the two via the ioe_errno
and ioe_description
fields:
import System.IO
import GHC.IO.Exception
import Control.Exception
foo path = do
h <- openFile path ReadMode
hClose h
show_ioe :: IOException -> IO ()
show_ioe e = do
putStrLn $ "errno: " ++ show (ioe_errno e)
putStrLn $ "description: " ++ ioe_description e
bar path = foo path `catch` show_ioe
Sample ghci session:
*Main> bar "/bin"
errno: Nothing
description: is a directory
*Main> bar "/bin/ls/asd"
errno: Just 20
description: Not a directory
- Once I have an exception, how should I go about extracting information from it?
Each exception has its own structure. The definition of an IOException may be found here.
To bring the field accessors into scope you need to import GHC.IO.Exception
.
- Is there a portable way of catching the GHC-only exceptions such as InappropriateType?
As @dfeuer said, for all practical purposes GHC is the only Haskell implementation at this time.
Update
Results from running your program. I didn't include the last result because I didn't have a read-only filesystem around to test it on, but I'm sure the error would be the same.
ioe_filename = Just "/nonexistent", ioe_description = "No such file or directory", ioe_errno = Just 2
ioe_filename = Just "/dev/full", ioe_description = "Permission denied", ioe_errno = Just 13
ioe_filename = Just "/root", ioe_description = "is a directory", ioe_errno = Nothing
ioe_filename = Just "/bin", ioe_description = "is a directory", ioe_errno = Nothing
ioe_filename = Just "/bin", ioe_description = "Is a directory", ioe_errno = Just 21
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/bin/ls/asdf", ioe_description = "Not a directory", ioe_errno = Just 20
ioe_filename = Just "/dev/null", ioe_description = "not a regular file", ioe_errno = Nothing
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With