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:
- Which exceptions should I be catching for
readFile
/writeFile
? - Once I have an exception, how should I go about extracting information from it?
- Is there a portable way of catching the GHC-only exceptions such as
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