Blame Network.hs

Packit 090c59
{-# LANGUAGE CPP #-}
Packit 090c59
-----------------------------------------------------------------------------
Packit 090c59
-- |
Packit 090c59
-- Module      :  Network
Packit 090c59
-- Copyright   :  (c) The University of Glasgow 2001
Packit 090c59
-- License     :  BSD-style (see the file libraries/network/LICENSE)
Packit 090c59
--
Packit 090c59
-- Maintainer  :  libraries@haskell.org
Packit 090c59
-- Stability   :  provisional
Packit 090c59
-- Portability :  portable
Packit 090c59
--
Packit 090c59
-- This module is kept for backwards-compatibility. New users are
Packit 090c59
-- encouraged to use "Network.Socket" instead.
Packit 090c59
--
Packit 090c59
-- "Network" was intended as a \"higher-level\" interface to networking
Packit 090c59
-- facilities, and only supports TCP.
Packit 090c59
--
Packit 090c59
-----------------------------------------------------------------------------
Packit 090c59
Packit 090c59
#include "HsNetworkConfig.h"
Packit 090c59
Packit 090c59
#ifdef HAVE_GETADDRINFO
Packit 090c59
-- Use IPv6-capable function definitions if the OS supports it.
Packit 090c59
#define IPV6_SOCKET_SUPPORT 1
Packit 090c59
#endif
Packit 090c59
Packit 090c59
module Network
Packit 090c59
    (
Packit 090c59
    -- * Basic data types
Packit 090c59
      Socket
Packit 090c59
    , PortID(..)
Packit 090c59
    , HostName
Packit 090c59
    , PortNumber
Packit 090c59
Packit 090c59
    -- * Initialisation
Packit 090c59
    , withSocketsDo
Packit 090c59
Packit 090c59
    -- * Server-side connections
Packit 090c59
    , listenOn
Packit 090c59
    , accept
Packit 090c59
    , sClose
Packit 090c59
Packit 090c59
    -- * Client-side connections
Packit 090c59
    , connectTo
Packit 090c59
Packit 090c59
    -- * Simple sending and receiving
Packit 090c59
    {-$sendrecv-}
Packit 090c59
    , sendTo
Packit 090c59
    , recvFrom
Packit 090c59
Packit 090c59
    -- * Miscellaneous
Packit 090c59
    , socketPort
Packit 090c59
Packit 090c59
    -- * Networking Issues
Packit 090c59
    -- ** Buffering
Packit 090c59
    {-$buffering-}
Packit 090c59
Packit 090c59
    -- ** Improving I\/O Performance over sockets
Packit 090c59
    {-$performance-}
Packit 090c59
    ) where
Packit 090c59
Packit 090c59
import Control.Monad (liftM)
Packit 090c59
import Data.Maybe (fromJust)
Packit 090c59
import Network.BSD
Packit 090c59
import Network.Socket hiding (accept, socketPort, recvFrom,
Packit 090c59
                              sendTo, PortNumber, sClose)
Packit 090c59
import qualified Network.Socket as Socket (accept)
Packit 090c59
import System.IO
Packit 090c59
import Prelude
Packit 090c59
import qualified Control.Exception as Exception
Packit 090c59
Packit 090c59
-- ---------------------------------------------------------------------------
Packit 090c59
-- High Level ``Setup'' functions
Packit 090c59
Packit 090c59
-- If the @PortID@ specifies a unix family socket and the @Hostname@
Packit 090c59
-- differs from that returned by @getHostname@ then an error is
Packit 090c59
-- raised. Alternatively an empty string may be given to @connectTo@
Packit 090c59
-- signalling that the current hostname applies.
Packit 090c59
Packit 090c59
data PortID =
Packit 090c59
          Service String                -- Service Name eg "ftp"
Packit 090c59
        | PortNumber PortNumber         -- User defined Port Number
Packit 090c59
#if !defined(mingw32_HOST_OS)
Packit 090c59
        | UnixSocket String             -- Unix family socket in file system
Packit 090c59
#endif
Packit 090c59
        deriving (Show, Eq)
Packit 090c59
Packit 090c59
-- | Calling 'connectTo' creates a client side socket which is
Packit 090c59
-- connected to the given host and port.  The Protocol and socket type is
Packit 090c59
-- derived from the given port identifier.  If a port number is given
Packit 090c59
-- then the result is always an internet family 'Stream' socket.
Packit 090c59
Packit 090c59
connectTo :: HostName           -- Hostname
Packit 090c59
          -> PortID             -- Port Identifier
Packit 090c59
          -> IO Handle          -- Connected Socket
Packit 090c59
Packit 090c59
#if defined(IPV6_SOCKET_SUPPORT)
Packit 090c59
-- IPv6 and IPv4.
Packit 090c59
Packit 090c59
connectTo hostname (Service serv) = connect' "Network.connectTo" hostname serv
Packit 090c59
Packit 090c59
connectTo hostname (PortNumber port) = connect' "Network.connectTo" hostname (show port)
Packit 090c59
#else
Packit 090c59
-- IPv4 only.
Packit 090c59
Packit 090c59
connectTo hostname (Service serv) = do
Packit 090c59
    proto <- getProtocolNumber "tcp"
Packit 090c59
    bracketOnError
Packit 090c59
        (socket AF_INET Stream proto)
Packit 090c59
        (sClose)  -- only done if there's an error
Packit 090c59
        (\sock -> do
Packit 090c59
          port  <- getServicePortNumber serv
Packit 090c59
          he    <- getHostByName hostname
Packit 090c59
          connect sock (SockAddrInet port (hostAddress he))
Packit 090c59
          socketToHandle sock ReadWriteMode
Packit 090c59
        )
Packit 090c59
Packit 090c59
connectTo hostname (PortNumber port) = do
Packit 090c59
    proto <- getProtocolNumber "tcp"
Packit 090c59
    bracketOnError
Packit 090c59
        (socket AF_INET Stream proto)
Packit 090c59
        (sClose)  -- only done if there's an error
Packit 090c59
        (\sock -> do
Packit 090c59
          he <- getHostByName hostname
Packit 090c59
          connect sock (SockAddrInet port (hostAddress he))
Packit 090c59
          socketToHandle sock ReadWriteMode
Packit 090c59
        )
Packit 090c59
#endif
Packit 090c59
Packit 090c59
#if !defined(mingw32_HOST_OS)
Packit 090c59
connectTo _ (UnixSocket path) = do
Packit 090c59
    bracketOnError
Packit 090c59
        (socket AF_UNIX Stream 0)
Packit 090c59
        (sClose)
Packit 090c59
        (\sock -> do
Packit 090c59
          connect sock (SockAddrUnix path)
Packit 090c59
          socketToHandle sock ReadWriteMode
Packit 090c59
        )
Packit 090c59
#endif
Packit 090c59
Packit 090c59
#if defined(IPV6_SOCKET_SUPPORT)
Packit 090c59
connect' :: String -> HostName -> ServiceName -> IO Handle
Packit 090c59
Packit 090c59
connect' caller host serv = do
Packit 090c59
    proto <- getProtocolNumber "tcp"
Packit 090c59
    let hints = defaultHints { addrFlags = [AI_ADDRCONFIG]
Packit 090c59
                             , addrProtocol = proto
Packit 090c59
                             , addrSocketType = Stream }
Packit 090c59
    addrs <- getAddrInfo (Just hints) (Just host) (Just serv)
Packit 090c59
    firstSuccessful caller $ map tryToConnect addrs
Packit 090c59
  where
Packit 090c59
  tryToConnect addr =
Packit 090c59
    bracketOnError
Packit 090c59
        (socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr))
Packit 090c59
        (sClose)  -- only done if there's an error
Packit 090c59
        (\sock -> do
Packit 090c59
          connect sock (addrAddress addr)
Packit 090c59
          socketToHandle sock ReadWriteMode
Packit 090c59
        )
Packit 090c59
#endif
Packit 090c59
Packit 090c59
-- | Creates the server side socket which has been bound to the
Packit 090c59
-- specified port.
Packit 090c59
--
Packit 090c59
-- 'maxListenQueue' (typically 128) is specified to the listen queue.
Packit 090c59
-- This is good enough for normal network servers but is too small
Packit 090c59
-- for high performance servers.
Packit 090c59
--
Packit 090c59
-- To avoid the \"Address already in use\" problems,
Packit 090c59
-- the 'ReuseAddr' socket option is set on the listening socket.
Packit 090c59
--
Packit 090c59
-- If available, the 'IPv6Only' socket option is set to 0
Packit 090c59
-- so that both IPv4 and IPv6 can be accepted with this socket.
Packit 090c59
--
Packit 090c59
-- If you don't like the behavior above, please use the lower level
Packit 090c59
-- 'Network.Socket.listen' instead.
Packit 090c59
Packit 090c59
listenOn :: PortID      -- ^ Port Identifier
Packit 090c59
         -> IO Socket   -- ^ Listening Socket
Packit 090c59
Packit 090c59
#if defined(IPV6_SOCKET_SUPPORT)
Packit 090c59
-- IPv6 and IPv4.
Packit 090c59
Packit 090c59
listenOn (Service serv) = listen' serv
Packit 090c59
Packit 090c59
listenOn (PortNumber port) = listen' (show port)
Packit 090c59
#else
Packit 090c59
-- IPv4 only.
Packit 090c59
Packit 090c59
listenOn (Service serv) = do
Packit 090c59
    proto <- getProtocolNumber "tcp"
Packit 090c59
    bracketOnError
Packit 090c59
        (socket AF_INET Stream proto)
Packit 090c59
        (sClose)
Packit 090c59
        (\sock -> do
Packit 090c59
            port    <- getServicePortNumber serv
Packit 090c59
            setSocketOption sock ReuseAddr 1
Packit 090c59
            bind sock (SockAddrInet port iNADDR_ANY)
Packit 090c59
            listen sock maxListenQueue
Packit 090c59
            return sock
Packit 090c59
        )
Packit 090c59
Packit 090c59
listenOn (PortNumber port) = do
Packit 090c59
    proto <- getProtocolNumber "tcp"
Packit 090c59
    bracketOnError
Packit 090c59
        (socket AF_INET Stream proto)
Packit 090c59
        (sClose)
Packit 090c59
        (\sock -> do
Packit 090c59
            setSocketOption sock ReuseAddr 1
Packit 090c59
            bind sock (SockAddrInet port iNADDR_ANY)
Packit 090c59
            listen sock maxListenQueue
Packit 090c59
            return sock
Packit 090c59
        )
Packit 090c59
#endif
Packit 090c59
Packit 090c59
#if !defined(mingw32_HOST_OS)
Packit 090c59
listenOn (UnixSocket path) =
Packit 090c59
    bracketOnError
Packit 090c59
        (socket AF_UNIX Stream 0)
Packit 090c59
        (sClose)
Packit 090c59
        (\sock -> do
Packit 090c59
            setSocketOption sock ReuseAddr 1
Packit 090c59
            bind sock (SockAddrUnix path)
Packit 090c59
            listen sock maxListenQueue
Packit 090c59
            return sock
Packit 090c59
        )
Packit 090c59
#endif
Packit 090c59
Packit 090c59
#if defined(IPV6_SOCKET_SUPPORT)
Packit 090c59
listen' :: ServiceName -> IO Socket
Packit 090c59
Packit 090c59
listen' serv = do
Packit 090c59
    proto <- getProtocolNumber "tcp"
Packit 090c59
    -- We should probably specify addrFamily = AF_INET6 and the filter
Packit 090c59
    -- code below should be removed. AI_ADDRCONFIG is probably not
Packit 090c59
    -- necessary. But this code is well-tested. So, let's keep it.
Packit 090c59
    let hints = defaultHints { addrFlags = [AI_ADDRCONFIG, AI_PASSIVE]
Packit 090c59
                             , addrSocketType = Stream
Packit 090c59
                             , addrProtocol = proto }
Packit 090c59
    addrs <- getAddrInfo (Just hints) Nothing (Just serv)
Packit 090c59
    -- Choose an IPv6 socket if exists.  This ensures the socket can
Packit 090c59
    -- handle both IPv4 and IPv6 if v6only is false.
Packit 090c59
    let addrs' = filter (\x -> addrFamily x == AF_INET6) addrs
Packit 090c59
        addr = if null addrs' then head addrs else head addrs'
Packit 090c59
    bracketOnError
Packit 090c59
        (socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr))
Packit 090c59
        (sClose)
Packit 090c59
        (\sock -> do
Packit 090c59
            setSocketOption sock ReuseAddr 1
Packit 090c59
            bind sock (addrAddress addr)
Packit 090c59
            listen sock maxListenQueue
Packit 090c59
            return sock
Packit 090c59
        )
Packit 090c59
#endif
Packit 090c59
Packit 090c59
-- -----------------------------------------------------------------------------
Packit 090c59
-- accept
Packit 090c59
Packit 090c59
-- | Accept a connection on a socket created by 'listenOn'.  Normal
Packit 090c59
-- I\/O operations (see "System.IO") can be used on the 'Handle'
Packit 090c59
-- returned to communicate with the client.
Packit 090c59
-- Notice that although you can pass any Socket to Network.accept,
Packit 090c59
-- only sockets of either AF_UNIX, AF_INET, or AF_INET6 will work
Packit 090c59
-- (this shouldn't be a problem, though). When using AF_UNIX, HostName
Packit 090c59
-- will be set to the path of the socket and PortNumber to -1.
Packit 090c59
--
Packit 090c59
accept :: Socket                -- ^ Listening Socket
Packit 090c59
       -> IO (Handle,
Packit 090c59
              HostName,
Packit 090c59
              PortNumber)       -- ^ Triple of: read\/write 'Handle' for
Packit 090c59
                                -- communicating with the client,
Packit 090c59
                                -- the 'HostName' of the peer socket, and
Packit 090c59
                                -- the 'PortNumber' of the remote connection.
Packit 090c59
accept sock@(MkSocket _ AF_INET _ _ _) = do
Packit 090c59
 ~(sock', (SockAddrInet port haddr)) <- Socket.accept sock
Packit 090c59
 peer <- catchIO
Packit 090c59
          (do
Packit 090c59
             (HostEntry peer _ _ _) <- getHostByAddr AF_INET haddr
Packit 090c59
             return peer
Packit 090c59
          )
Packit 090c59
          (\_e -> inet_ntoa haddr)
Packit 090c59
                -- if getHostByName fails, we fall back to the IP address
Packit 090c59
 handle <- socketToHandle sock' ReadWriteMode
Packit 090c59
 return (handle, peer, port)
Packit 090c59
#if defined(IPV6_SOCKET_SUPPORT)
Packit 090c59
accept sock@(MkSocket _ AF_INET6 _ _ _) = do
Packit 090c59
 (sock', addr) <- Socket.accept sock
Packit 090c59
 peer <- catchIO ((fromJust . fst) `liftM` getNameInfo [] True False addr) $
Packit 090c59
         \_ -> case addr of
Packit 090c59
                 SockAddrInet  _   a   -> inet_ntoa a
Packit 090c59
                 SockAddrInet6 _ _ a _ -> return (show a)
Packit 090c59
#if defined(mingw32_HOST_OS)
Packit 090c59
                 SockAddrUnix {}       -> ioError $ userError "Network.accept: peer socket address 'SockAddrUnix' not supported on this platform."
Packit 090c59
#else
Packit 090c59
                 SockAddrUnix      a   -> return a
Packit 090c59
#endif
Packit 090c59
#if defined(CAN_SOCKET_SUPPORT)
Packit 090c59
                 SockAddrCan {}        -> ioError $ userError "Network.accept: peer socket address 'SockAddrCan' not supported."
Packit 090c59
#else
Packit 090c59
                 SockAddrCan {}        -> ioError $ userError "Network.accept: peer socket address 'SockAddrCan' not supported on this platform."
Packit 090c59
#endif
Packit 090c59
 handle <- socketToHandle sock' ReadWriteMode
Packit 090c59
 let port = case addr of
Packit 090c59
              SockAddrInet  p _     -> p
Packit 090c59
              SockAddrInet6 p _ _ _ -> p
Packit 090c59
              _                     -> -1
Packit 090c59
 return (handle, peer, port)
Packit 090c59
#endif
Packit 090c59
#if !defined(mingw32_HOST_OS)
Packit 090c59
accept sock@(MkSocket _ AF_UNIX _ _ _) = do
Packit 090c59
 ~(sock', (SockAddrUnix path)) <- Socket.accept sock
Packit 090c59
 handle <- socketToHandle sock' ReadWriteMode
Packit 090c59
 return (handle, path, -1)
Packit 090c59
#endif
Packit 090c59
accept (MkSocket _ family _ _ _) =
Packit 090c59
  ioError $ userError $ "Network.accept: address family '" ++
Packit 090c59
    show family ++ "' not supported."
Packit 090c59
Packit 090c59
Packit 090c59
-- | Close the socket. Sending data to or receiving data from closed socket
Packit 090c59
-- may lead to undefined behaviour.
Packit 090c59
sClose :: Socket -> IO ()
Packit 090c59
sClose = close -- Explicit redefinition because Network.sClose is deprecated,
Packit 090c59
               -- hence the re-export would also be marked as such.
Packit 090c59
Packit 090c59
-- -----------------------------------------------------------------------------
Packit 090c59
-- sendTo/recvFrom
Packit 090c59
Packit 090c59
{-$sendrecv
Packit 090c59
Send and receive data from\/to the given host and port number.  These
Packit 090c59
should normally only be used where the socket will not be required for
Packit 090c59
further calls. Also, note that due to the use of 'hGetContents' in 'recvFrom'
Packit 090c59
the socket will remain open (i.e. not available) even if the function already
Packit 090c59
returned. Their use is strongly discouraged except for small test-applications
Packit 090c59
or invocations from the command line.
Packit 090c59
-}
Packit 090c59
Packit 090c59
sendTo :: HostName      -- Hostname
Packit 090c59
       -> PortID        -- Port Number
Packit 090c59
       -> String        -- Message to send
Packit 090c59
       -> IO ()
Packit 090c59
sendTo h p msg = do
Packit 090c59
  s <- connectTo h p
Packit 090c59
  hPutStr s msg
Packit 090c59
  hClose s
Packit 090c59
Packit 090c59
recvFrom :: HostName    -- Hostname
Packit 090c59
         -> PortID      -- Port Number
Packit 090c59
         -> IO String   -- Received Data
Packit 090c59
Packit 090c59
#if defined(IPV6_SOCKET_SUPPORT)
Packit 090c59
recvFrom host port = do
Packit 090c59
    proto <- getProtocolNumber "tcp"
Packit 090c59
    let hints = defaultHints { addrFlags = [AI_ADDRCONFIG]
Packit 090c59
                             , addrProtocol = proto
Packit 090c59
                             , addrSocketType = Stream }
Packit 090c59
    allowed <- map addrAddress `liftM` getAddrInfo (Just hints) (Just host)
Packit 090c59
                                                   Nothing
Packit 090c59
    s <- listenOn port
Packit 090c59
    let waiting = do
Packit 090c59
        (s', addr) <- Socket.accept s
Packit 090c59
        if not (addr `oneOf` allowed)
Packit 090c59
         then sClose s' >> waiting
Packit 090c59
         else socketToHandle s' ReadMode >>= hGetContents
Packit 090c59
    waiting
Packit 090c59
  where
Packit 090c59
    a@(SockAddrInet _ ha) `oneOf` ((SockAddrInet _ hb):bs)
Packit 090c59
        | ha == hb = True
Packit 090c59
        | otherwise = a `oneOf` bs
Packit 090c59
    a@(SockAddrInet6 _ _ ha _) `oneOf` ((SockAddrInet6 _ _ hb _):bs)
Packit 090c59
        | ha == hb = True
Packit 090c59
        | otherwise = a `oneOf` bs
Packit 090c59
    _ `oneOf` _ = False
Packit 090c59
#else
Packit 090c59
recvFrom host port = do
Packit 090c59
 ip  <- getHostByName host
Packit 090c59
 let ipHs = hostAddresses ip
Packit 090c59
 s   <- listenOn port
Packit 090c59
 let
Packit 090c59
  waiting = do
Packit 090c59
     ~(s', SockAddrInet _ haddr)  <-  Socket.accept s
Packit 090c59
     he <- getHostByAddr AF_INET haddr
Packit 090c59
     if not (any (`elem` ipHs) (hostAddresses he))
Packit 090c59
      then do
Packit 090c59
         sClose s'
Packit 090c59
         waiting
Packit 090c59
      else do
Packit 090c59
        h <- socketToHandle s' ReadMode
Packit 090c59
        msg <- hGetContents h
Packit 090c59
        return msg
Packit 090c59
Packit 090c59
 message <- waiting
Packit 090c59
 return message
Packit 090c59
#endif
Packit 090c59
Packit 090c59
-- ---------------------------------------------------------------------------
Packit 090c59
-- Access function returning the port type/id of socket.
Packit 090c59
Packit 090c59
-- | Returns the 'PortID' associated with a given socket.
Packit 090c59
socketPort :: Socket -> IO PortID
Packit 090c59
socketPort s = do
Packit 090c59
    sockaddr <- getSocketName s
Packit 090c59
    case sockaddr of
Packit 090c59
      SockAddrInet port _      -> return $ PortNumber port
Packit 090c59
#if defined(IPV6_SOCKET_SUPPORT)
Packit 090c59
      SockAddrInet6 port _ _ _ -> return $ PortNumber port
Packit 090c59
#else
Packit 090c59
      SockAddrInet6 {}         -> ioError $ userError "Network.socketPort: socket address 'SockAddrInet6' not supported on this platform."
Packit 090c59
#endif
Packit 090c59
#if defined(mingw32_HOST_OS)
Packit 090c59
      SockAddrUnix {}          -> ioError $ userError "Network.socketPort: socket address 'SockAddrUnix' not supported on this platform."
Packit 090c59
#else
Packit 090c59
      SockAddrUnix path        -> return $ UnixSocket path
Packit 090c59
#endif
Packit 090c59
      SockAddrCan {}           -> ioError $ userError "Network.socketPort: socket address 'SockAddrCan' not supported."
Packit 090c59
Packit 090c59
-- ---------------------------------------------------------------------------
Packit 090c59
-- Utils
Packit 090c59
Packit 090c59
-- Like bracket, but only performs the final action if there was an
Packit 090c59
-- exception raised by the middle bit.
Packit 090c59
bracketOnError
Packit 090c59
        :: IO a         -- ^ computation to run first (\"acquire resource\")
Packit 090c59
        -> (a -> IO b)  -- ^ computation to run last (\"release resource\")
Packit 090c59
        -> (a -> IO c)  -- ^ computation to run in-between
Packit 090c59
        -> IO c         -- returns the value from the in-between computation
Packit 090c59
bracketOnError = Exception.bracketOnError
Packit 090c59
Packit 090c59
-----------------------------------------------------------------------------
Packit 090c59
-- Extra documentation
Packit 090c59
Packit 090c59
{-$buffering
Packit 090c59
Packit 090c59
The 'Handle' returned by 'connectTo' and 'accept' is block-buffered by
Packit 090c59
default.  For an interactive application you may want to set the
Packit 090c59
buffering mode on the 'Handle' to
Packit 090c59
'LineBuffering' or 'NoBuffering', like so:
Packit 090c59
Packit 090c59
> h <- connectTo host port
Packit 090c59
> hSetBuffering h LineBuffering
Packit 090c59
-}
Packit 090c59
Packit 090c59
{-$performance
Packit 090c59
Packit 090c59
For really fast I\/O, it might be worth looking at the 'hGetBuf' and
Packit 090c59
'hPutBuf' family of functions in "System.IO".
Packit 090c59
-}
Packit 090c59
Packit 090c59
catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a
Packit 090c59
#if MIN_VERSION_base(4,0,0)
Packit 090c59
catchIO = Exception.catch
Packit 090c59
#else
Packit 090c59
catchIO = Exception.catchJust Exception.ioErrors
Packit 090c59
#endif
Packit 090c59
Packit 090c59
-- Version of try implemented in terms of the locally defined catchIO
Packit 090c59
tryIO :: IO a -> IO (Either Exception.IOException a)
Packit 090c59
tryIO m = catchIO (liftM Right m) (return . Left)
Packit 090c59
Packit 090c59
-- Returns the first action from a list which does not throw an exception.
Packit 090c59
-- If all the actions throw exceptions (and the list of actions is not empty),
Packit 090c59
-- the last exception is thrown.
Packit 090c59
-- The operations are run outside of the catchIO cleanup handler because
Packit 090c59
-- catchIO masks asynchronous exceptions in the cleanup handler.
Packit 090c59
-- In the case of complete failure, the last exception is actually thrown.
Packit 090c59
firstSuccessful :: String -> [IO a] -> IO a
Packit 090c59
firstSuccessful caller = go Nothing
Packit 090c59
  where
Packit 090c59
  -- Attempt the next operation, remember exception on failure
Packit 090c59
  go _ (p:ps) =
Packit 090c59
    do r <- tryIO p
Packit 090c59
       case r of
Packit 090c59
         Right x -> return x
Packit 090c59
         Left  e -> go (Just e) ps
Packit 090c59
Packit 090c59
  -- All operations failed, throw error if one exists
Packit 090c59
  go Nothing  [] = ioError $ userError $ caller ++ ": firstSuccessful: empty list"
Packit 090c59
  go (Just e) [] = Exception.throwIO e