packages feed

fudgets-0.18.3: hsrc/ghc-dialogue/AsyncInput.hs

{-# LANGUAGE CPP, MagicHash, UnboxedTuples #-}
{- Obsolete OPTIONS -optc-I/usr/X11R6/include -optc-DNON_POSIX_SOURCE -fvia-C -}
--
module AsyncInput
   (doSocketRequest,doSelect,getAsyncInput',initXCall,XCallState)
  where
import P_IO_data({-Request(..),-}Response(..))
import Xtypes
import Sockets as S
import DLValue
import Unsafe.Coerce -- !!!
--import ResourceIds
import Utils(swap)

import XCallTypes
import StructFuns
import Xlib
import EncodeEvent
import Marshall
import MyForeign
import GHC.Exts(addrToAny# )
import GHC.Ptr(FunPtr(..))

--import Ap
import HbcUtils(lookupWithDefault)
import Data.Maybe(mapMaybe)
import Control.Monad(when)
import Data.Traversable(traverse)

import PQueue

import Data.IORef(newIORef,readIORef,writeIORef,IORef)
import System.Posix.DynamicLinker as DL

import PackedString(unpackPS,lengthPS{-,packCBytesST,psToByteArray-})

default (Int)

#include "newstructfuns.h"

H_STRUCTTYPE(fd_set)

allocaInt = allocaElem (0::Int)

type AiTable = [(Fd,Descriptor)]

type IOVar a = IORef a

newIOVar = newIORef
readIOVar = readIORef
writeIOVar = writeIORef

type MsTime = Int

data XCallState = XCallState
      Cfd_set -- ^ read fd_set
      (IOVar Fd) -- ^ highest fd in read fd_set
      (IOVar AiTable)
      (IOVar (PQueue MsTime (MsTime,Timer)))
      (IOVar Time)

initXCall =
 XCallState
  <$> newPtr
  <*> newIOVar 0
  <*> newIOVar []
  <*> newIOVar empty
  <*> newIOVar 0

type Fd = Int32

foreign import ccall "unistd.h read" cread :: Fd -> Addr -> CSize -> IO CSize
foreign import ccall "unistd.h write" cwrite :: Fd -> CString -> CSize -> IO CSize
foreign import ccall "sys/socket.h" accept :: Fd -> CsockAddr -> Addr -> IO Fd

getAsyncInput' (XCallState fds maxfdvar aitable tq tno) =
 AsyncInput <$> do
   ai <- readIOVar aitable
   let timeLeft =
	  do tqv <- readIOVar tq
	     case inspect tqv of
	       Nothing -> return Nothing
	       Just ((t,_),_) ->
		 do ms <- mstime
		    return . Just . max 0 $ t - ms
   
   let doSelect = do
	  timeout <- traverse newTimeVal =<< timeLeft
          readfds <- newPtr
--	  _casm_ ``bcopy(%0,%1,sizeof(fd_set));'' fds readfds
	  bcopy_fdset fds readfds
          maxfdvar <- readIOVar maxfdvar
	  n <- select (maxfdvar+1) readfds timeout
	  maybe (return ()) freePtr timeout
	  let findFd fd = do
		  s <- fromC <$>
--		       _casm_ ``%r=FD_ISSET(%0,(fd_set*)%1);'' fd readfds
		       fd_isset fd readfds
		  if not s then findFd (fd+1) 
		    else let d = lookAi fd in case d of
		       DisplayDe _ -> mkEvent fd d
		       SocketDe _ ->
		         let bufsize = 2000
			 in alloca bufsize $ \ buf ->
			 do got <- tryP "read" (>=0) $ cread fd buf (fromIntegral bufsize)
			    str <- unmarshallString' (CString buf) (fromIntegral got)
			    return (d,SocketRead str)
		       LSocketDe _ ->
		         allocaInt $ \ addrlen ->
			 do addr <- newsockAddr
			    sfd <- tryP "accept" (>=0) $ 
				--_ccall_ ACCEPT fd addr addrlen
				accept fd addr addrlen
			    sf <- getfilep sfd "r+"
			    --buf <- stToIO $ newCharArray (1,1000)
			    --tryP "hostName" (==0) $ _ccall_ hostName addr buf
			    --peer <- cstring <$> mutByteArr2Addr buf
			    let peer = ""
			    return (d,SocketAccepted (So sf) peer)

		       _ -> error "getAsyncInput3"
          e <- if n == -1 then error . ("select "++) =<< strerror =<< errno
	       else if n > 0 then findFd 0
	            else do tno <- removetimeq tq
	                    return (TimerDe tno,TimerAlarm)
	  freePtr readfds
	  return e

       mkEvent fd d = do
           (window,fev) <- {-motionCompress display =<<-} getNextEvent display
	   return (descriptor,XEvent (window,fev))
         where descriptor@(DisplayDe display) = d
         
       lookAi = lookupWithDefault ai (error "getAsyncInput2")
       dispde x@(fd,DisplayDe _) = Just x
       dispde _ = Nothing
   case mapMaybe dispde ai of
       [] -> doSelect
       (fd,d@(DisplayDe display)):_ -> do
         q <- xPending display
         if q>0 then mkEvent fd d else doSelect

newTimeVal tleft =
  do timeout <- newPtr
     SET(timeVal,Int,timeout,tv_usec,(tleft `mod` 1000) * 1000)
     SET(timeVal,Int,timeout,tv_sec,(tleft `div` 1000))
     return timeout

foreign import ccall "sys/select.h select" cselect :: Int32 -> Cfd_set -> Cfd_set -> Cfd_set -> CtimeVal -> IO Int32
--foreign import ccall "unistd.h" getdtablesize :: IO Int

select :: Int32 -> Cfd_set -> Maybe CtimeVal -> IO Int32
select nfds readfds timeout = start where
 start = do
    n <- cselect nfds readfds nullPtr nullPtr (maybe nullPtr id timeout)
{-
      case timeout of
        Nothing -> 
           _casm_ ``%r=select(getdtablesize(),%0,NULL,NULL,NULL);'' readfds
	Just t -> 
           _casm_ ``%r=select(getdtablesize(),%0,NULL,NULL,%1);'' readfds t
-}
    
    if n /= -1 then return n else do
       e <- errno
       if e == CCONST(EINTR) --- || e == CCONST(EAGAIN)
         then start -- again
         else return n
--}
foreign import ccall "asyncinput.h get_errno" errno :: IO Int
--errno :: IO Int
--errno = _casm_ ``%r=errno;''

foreign import ccall "sys/socket.h" listen :: Fd -> Int32 -> IO Int
foreign import ccall "sys/socket.h" socket :: Int32 -> Int32 -> Int32 -> IO Fd
foreign import ccall "stdio.h" fopen :: CString -> CString -> IO Int -- hmm
foreign import ccall "stdio.h" fclose :: Int -> IO Int

foreign import ccall "asyncinput.h" in_connect :: CString -> Int32 -> Int32 -> IO Int32
foreign import ccall "asyncinput.h" in_bind :: Int32 -> Int32 -> IO Fd
foreign import ccall "asyncinput.h" get_stdin :: IO Int

doSocketRequest (XCallState fds maxfdvar aitable tq tno) sr =
 case sr of
   CreateTimer interval first -> do
          tqv <- readIOVar tq
          tnov <- readIOVar tno
	  now <- mstime
	  writeIOVar tq (insert tqv (now+first,(interval,Ti tnov)))
	  writeIOVar tno (tnov+1)
	  returnS (Timer (Ti tnov))
   DestroyTimer t -> do
          tqv <- readIOVar tq
	  writeIOVar tq (remove tqv t)
	  return Success
   OpenSocket host port -> do
     chost <- marshallString host
     s <- tryP "in_connect" (>=0) $ 
--           _casm_ ``%r=in_connect(%0,%1,SOCK_STREAM);'' chost port
           in_connect chost (fromIntegral port) (fromIntegral CCONST(SOCK_STREAM))
     sf <- getfilep s "r+"
     freePtr chost
     returnS (Socket (So sf))
   OpenLSocket port -> do
      s <- tryP "in_bind" (>=0) $ if port == 0 
--         then _casm_ ``%r=socket(AF_INET,SOCK_STREAM,0);''
           then socket (fromIntegral CCONST(AF_INET)) (fromIntegral CCONST(SOCK_STREAM)) 0
--	   else _casm_ ``%r=in_bind(%0,SOCK_STREAM);'' port
	   else in_bind (fromIntegral port) (fromIntegral CCONST(SOCK_STREAM))
      tryP "listen" (==0) $ listen s 5
      SocketResponse . LSocket . LSo <$> getfilep s "r+"
   WriteSocket s str -> writeSocket s str
   WriteSocketPS s str ->
      do writeSocket s (unpackPS str) -- grr!
         returnS (Wrote (lengthPS str)) -- grr!
{-
     do
      fd <- fileno s
      r <- tryP "WriteSocket[out]" (>=0) $ 
        _casm_ ``%r=write(fileno((FILE*)%0),%1,%2);'' s (psToByteArray str) (lengthPS str)
--        bawrite fd (psToByteArray str) (lengthPS str)
      return (SocketResponse (Wrote r))
-}
   CloseSocket (So s) -> close s
   CloseLSocket (LSo s) -> close s
   GetStdinSocket -> SocketResponse . Socket . So <$>
--        _casm_ ``%r=stdin;''
	get_stdin
   GetSocketName (So s) -> socketname s
   GetLSocketName (LSo s) -> socketname s
   StartProcess cmd doIn doOut doErr -> startProcess cmd doIn doOut doErr
   DLOpen path -> do dh <- dlopen path [RTLD_LAZY]
                     case dh of
                       Null -> failu =<< dlerror
                       _ -> returnS $ S.DLHandle (DL dh)
   DLClose (DL dh) -> do dlclose dh ; return Success
   DLSym (DL dh) name ->
       do FunPtr fp <- dlsym dh name
          case addrToAny# fp of
            (# hval #) -> returnS . DLVal $ DLValue (unsafeCoerce hval)
   OpenFileAsSocket name mode -> do
     cname <- marshallString name
     cmode <- marshallString mode
     s <- tryP "OpenSocketAsFile[fopen]" (/=0) $ fopen cname cmode
     freePtr cname
     freePtr cmode
     returnS $ (Socket . So) s
   _ -> error ("Not implemented: "++show sr)
 where returnS = return . SocketResponse
       close s = do
          fclose s
	  return Success
       socketname s =
          allocaInt $ \ lenp ->
          do sa <- newsockAddr
--	     tryP "GetLSocketName" (==0) $ _ccall_ GETSOCKNAME (s::Int) sa lenp
	     tryP "GetLSocketName" (==0) $ getsockname s sa lenp
	     len <- peek lenp
	     strp <- GETC(sockAddr,char *,CString,sa,sa_data)
--	     Str . unpackPS <$> (stToIO $ packCBytesST len strp)
	     Str <$> unmarshallString' strp len

writeSocket (So s) str =
   do let n = length str
      fd <- get_fileno s
      cstr <- marshallString' str n
      tryP "WriteSocket[out]" (>=0) $ 
--        _casm_ ``%r=write(fileno((FILE*)%0),%1,%2);'' s cstr n
        cwrite fd cstr (fromIntegral n)
      freePtr cstr
      return Success

foreign import ccall "sys/socket.h" getsockname :: Int -> CsockAddr -> Addr -> IO Int
foreign import ccall "stdio.h" fdopen :: Int32 -> CString -> IO Int

getfilep s mode = tryP "fdopen" (/=0) $
  do cmode <- marshallString (mode::String)
--     _ccall_ fdopen (s::Int) cmode :: IO Int
     fdopen s cmode :: IO Int

foreign import ccall "string.h strerror" cstrerror :: Int -> IO CString
strerror e = unmarshallString =<< cstrerror e

tryP e p io = do
    r <- io
    if p (r{-::Int-}) then return r else do
--      cstr <- _casm_ ``%r=strerror(errno);''
      s <- strerror =<< errno
      failu (e++": "++ s)


peekq tq = do
   tqv <- readIOVar tq
   return (inspect tqv)

removetimeq tq = do
   tqv <- readIOVar tq
   case inspect tqv of
     Just ((first,v@(interval,tnov)),tqv') -> do
        let tqv2 = if interval == 0 then tqv'
	           else insert tqv' (first+interval,v)
        writeIOVar tq tqv2
	return tnov
	
foreign import ccall "sys/time.h" gettimeofday :: CtimeVal -> Addr -> IO ()

mstime = do
    now <- newtimeVal
--    _casm_ ``gettimeofday(%0,NULL);'' now
    gettimeofday now nullAddr
    s <- GET(timeVal,Int,now,tv_sec)
    us <- GET(timeVal,Int,now,tv_usec)
    return (s * 1000 + us `div` 1000 :: Int)


foreign import ccall "asyncinput.h" fdzero :: Cfd_set -> IO ()
foreign import ccall "asyncinput.h fdset" fd_set :: Fd -> Cfd_set -> IO ()
foreign import ccall "asyncinput.h fdisset" fd_isset :: Fd -> Cfd_set -> IO Int
foreign import ccall "asyncinput.h" bcopy_fdset :: Cfd_set -> Cfd_set -> IO ()

doSelect :: XCallState -> [Descriptor] -> IO Response
doSelect (XCallState fds maxfdvar aitable _ _) dl =
 do
  fdzero fds
  ait <- concat <$> mapM descriptor dl
  let fds = map fst ait
  writeIOVar aitable ait
  writeIOVar maxfdvar (maximum fds)
  mapM_ fdset fds
  return Success
 where descriptor d = case d of
	   LSocketDe (LSo s) -> withd $ get_fileno s
	   SocketDe (So s) ->  withd $ get_fileno s
	   OutputSocketDe (So s) ->  withd $ get_fileno s
	   DisplayDe ({-Display-} d) -> withd $ 
--	         _casm_ ``%r=((Display*)%0)->fd;'' d
		 xConnectionNumber d -- hmm
	   TimerDe _ -> return []
           _ -> do putStr "Unexpected descriptor: ";print d;return []
          where withd m = m >>= \fd -> return [(fd,d)]

       fdset :: Fd -> IO ()
       --fdset s =  _casm_ ``FD_SET(%0,(fd_set*)%1);'' s fds
       fdset s =  fd_set s fds

foreign import ccall "asyncinput.h" get_fileno :: Int -> IO Fd
{-

-- Register problems with FD_ZERO under Redhat 6.1 Linux-i386...
--fdzero :: Cfd_set -> IO ()
--fdzero fds = _casm_ ``{ fd_set *s=%0; FD_ZERO(s);}'' fds
--fdzero fds = _ccall_ FD_ZERO fds

mutByteArr2Addr :: MutableByteArray RealWorld Int -> IO  Addr
mutByteArr2Addr arr  = _casm_ `` %r=(void *)%0; '' arr
--}

foreign import ccall "unistd.h" fork :: IO Int
foreign import ccall "unistd.h" execl :: CString -> CString -> CString -> CString -> Int -> IO Int
foreign import ccall "unistd.h" pipe :: Addr -> IO Int
foreign import ccall "unistd.h" dup :: Fd -> IO Fd
foreign import ccall "asyncinput.h" disable_timers :: IO ()

startProcess cmd doIn doOut doErr =
  do inPipe <- optPipe doIn
     outPipe <- optPipe doOut
     errPipe <- optPipe doErr
--     pid <- _ccall_ fork
     pid <- fork
     case pid::Int of
       -1 -> failu "fork" -- use tryP instead
       0 -> do -- child process
	       -- Disable virtual timer, used by the GHC RTS
	       disable_timers
	       optDupIn 0 inPipe
	       optDupOut 1 outPipe
	       optDupOut 2 errPipe
	       binsh <- marshallString "/bin/sh"
	       sh <- marshallString "sh"
	       dashc <- marshallString "-c"
	       ccmd <- marshallString cmd
	       execl binsh sh dashc ccmd (0::Int)
	       --_ccall_ _exit 1
	       failu "execl"
       _ -> do -- parent process
	       inS <- optPipeIn inPipe
	       outS <- optPipeOut outPipe
	       errS <- optPipeOut errPipe
	       return $ SocketResponse $ ProcessSockets inS outS errS
  where
    optPipe False = return Nothing
    optPipe True = Just `fmap` newPipe
    newPipe = do pa <- newArray 2
		 ok <- pipe (addrOf (pa::CInt32))
		 [p0,p1] <- readArray pa 2
		 freePtr pa
		 when (ok/=0) $ failu "pipe" -- use tryP instead
		 return (p0,p1)

    optDupIn d = optDupOut d . fmap swap
    optDupOut d = maybe (return ()) (dupOut d)
    dupOut d (p0,p1) = do cclose d
			  dup p1
			  cclose p0
			  cclose p1
			  return ()

    optPipeIn = optPipeS "w" . fmap swap
    optPipeOut = optPipeS "r"
    optPipeS m = maybe (return Nothing) (fmap Just . pipeS m)
    pipeS m (p0,p1) = do cclose p1
			 fmap So (getfilep p0 m)
			 

foreign import ccall "unistd.h close" cclose :: Int32 -> IO Int32