Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to handle readFile and writeFile exceptions

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 IOErrors 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:

  1. Which exceptions should I be catching for readFile/writeFile?
  2. Once I have an exception, how should I go about extracting information from it?
  3. 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
like image 462
Matthew Avatar asked Jun 28 '16 15:06

Matthew


1 Answers

  1. 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
  1. 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.

  1. 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
like image 159
ErikR Avatar answered Oct 28 '22 23:10

ErikR