||| Signal raising and handling. ||| ||| NOTE that there are important differences between ||| what can be done out-of-box in Windows and POSIX based ||| operating systems. This module tries to honor both ||| by putting things only available in POSIX environments ||| into appropriately named namespaces or data types. module System.Signal import Data.Fuel import Data.List import System.Errno %default total signalFFI : String -> String signalFFI fn = "C:" ++ fn ++ ", libidris2_support, idris_signal.h" -- -- Signals -- %foreign signalFFI "sighup" prim__sighup : Int %foreign signalFFI "sigint" prim__sigint : Int %foreign signalFFI "sigabrt" prim__sigabrt : Int %foreign signalFFI "sigquit" prim__sigquit : Int %foreign signalFFI "sigill" prim__sigill : Int %foreign signalFFI "sigsegv" prim__sigsegv : Int %foreign signalFFI "sigtrap" prim__sigtrap : Int %foreign signalFFI "sigfpe" prim__sigfpe : Int %foreign signalFFI "sigusr1" prim__sigusr1 : Int %foreign signalFFI "sigusr2" prim__sigusr2 : Int public export data PosixSignal = ||| Hangup (i.e. controlling terminal closed) SigHUP | ||| Quit SigQUIT | ||| Trap (as used by debuggers) SigTRAP | SigUser1 | SigUser2 export Eq PosixSignal where SigHUP == SigHUP = True SigQUIT == SigQUIT = True SigTRAP == SigTRAP = True SigUser1 == SigUser1 = True SigUser2 == SigUser2 = True _ == _ = False public export data Signal = ||| Interrupt (e.g. ctrl+c pressed) SigINT | ||| Abnormal termination SigABRT | ||| Ill-formed instruction SigILL | ||| Segmentation fault SigSEGV | ||| Floating-point error SigFPE | ||| Signals only available on POSIX operating systems SigPosix PosixSignal export Eq Signal where SigINT == SigINT = True SigABRT == SigABRT = True SigILL == SigILL = True SigSEGV == SigSEGV = True SigFPE == SigFPE = True SigPosix x == SigPosix y = x == y _ == _ = False signalCode : Signal -> Int signalCode SigINT = prim__sigint signalCode SigABRT = prim__sigabrt signalCode SigILL = prim__sigill signalCode SigSEGV = prim__sigsegv signalCode SigFPE = prim__sigfpe signalCode (SigPosix SigHUP ) = prim__sighup signalCode (SigPosix SigQUIT ) = prim__sigquit signalCode (SigPosix SigTRAP ) = prim__sigtrap signalCode (SigPosix SigUser1) = prim__sigusr1 signalCode (SigPosix SigUser2) = prim__sigusr2 toSignal : Int -> Maybe Signal toSignal (-1) = Nothing toSignal x = lookup x codes where codes : List (Int, Signal) codes = [ (prim__sigint , SigINT) , (prim__sigabrt, SigABRT) , (prim__sigill , SigILL) , (prim__sigsegv, SigSEGV) , (prim__sigfpe , SigFPE) , (prim__sighup , SigPosix SigHUP) , (prim__sigquit, SigPosix SigQUIT) , (prim__sigtrap, SigPosix SigTRAP) , (prim__sigusr1, SigPosix SigUser1) , (prim__sigusr2, SigPosix SigUser2) ] -- -- Signal Handling -- %foreign signalFFI "ignore_signal" prim__ignoreSignal : Int -> PrimIO Int %foreign signalFFI "default_signal" prim__defaultSignal : Int -> PrimIO Int %foreign signalFFI "collect_signal" prim__collectSignal : Int -> PrimIO Int %foreign signalFFI "handle_next_collected_signal" prim__handleNextCollectedSignal : PrimIO Int %foreign signalFFI "send_signal" prim__sendSignal : Int -> Int -> PrimIO Int %foreign signalFFI "raise_signal" prim__raiseSignal : Int -> PrimIO Int ||| An Error represented by a code. See ||| relevant `errno` documentation. ||| https://man7.org/linux/man-pages/man3/errno.3.html public export data SignalError = Error Int getError : HasIO io => io SignalError getError = Error <$> getErrno isError : Int -> Bool isError (-1) = True isError _ = False ||| Ignore the given signal. ||| Be careful doing this, as most signals have useful ||| default behavior -- you might want to set the signal ||| to its default behavior instead with `defaultSignal`. export ignoreSignal : HasIO io => Signal -> io (Either SignalError ()) ignoreSignal sig = do res <- primIO $ prim__ignoreSignal (signalCode sig) case isError res of False => pure $ Right () True => Left <$> getError ||| Use the default handler for the given signal. ||| You can use this function to unset custom ||| handling of a signal. export defaultSignal : HasIO io => Signal -> io (Either SignalError ()) defaultSignal sig = do res <- primIO $ prim__defaultSignal (signalCode sig) case isError res of False => pure $ Right () True => Left <$> getError ||| Collect the given signal. ||| ||| This replaces the existing handling of the given signal ||| and instead results in Idris collecting occurrences of ||| the signal until you call `handleNextCollectedSignal`. ||| ||| First, call `collectSignal` for any number of signals. ||| Then, call `handleNextCollectedSignal` in each main loop ||| of your program to retrieve (and mark as handled) the next ||| signal that was collected, if any. ||| ||| Signals are not queued, so the return order of signals is ||| not specified and signals may be deduplicated. export collectSignal : HasIO io => Signal -> io (Either SignalError ()) collectSignal sig = do res <- primIO $ prim__collectSignal (signalCode sig) case isError res of False => pure $ Right () True => Left <$> getError ||| Get next collected signal under the pretense of handling it. ||| ||| Calling this "marks" the signal as handled so the next time ||| this function is called you will retrieve the next unhandled ||| signal instead of the same signal again. ||| ||| You get back Nothing if there are no pending signals. export handleNextCollectedSignal : HasIO io => io (Maybe Signal) handleNextCollectedSignal = toSignal <$> primIO prim__handleNextCollectedSignal ||| Get many collected signals and mark them as handled. ||| ||| Use `forever` as your fuel if you don't want or need to ||| retain totality. Alternatively, pick a max number to ||| retrieve and use `limit/1` as your fuel. export handleManyCollectedSignals : HasIO io => Fuel -> io (List Signal) handleManyCollectedSignals Dry = pure [] handleManyCollectedSignals (More fuel) = do Just next <- handleNextCollectedSignal | Nothing => pure [] pure $ next :: !(handleManyCollectedSignals fuel) ||| Send a signal to the current process. export raiseSignal : HasIO io => Signal -> io (Either SignalError ()) raiseSignal sig = do res <- primIO $ prim__raiseSignal (signalCode sig) case isError res of False => pure $ Right () True => Left <$> getError namespace Posix ||| Send a signal to a POSIX process using a PID to identify the process. export sendSignal : HasIO io => Signal -> (pid : Int) -> io (Either SignalError ()) sendSignal sig pid = do res <- primIO $ prim__sendSignal pid (signalCode sig) case isError res of False => pure $ Right () True => Left <$> getError