2016-11-20 16:08:16 +00:00
|
|
|
{- P2P protocol, partial IO implementation
|
|
|
|
-
|
|
|
|
- Copyright 2016 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2016-11-30 20:38:16 +00:00
|
|
|
{-# LANGUAGE RankNTypes, ScopedTypeVariables, CPP #-}
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-22 18:34:49 +00:00
|
|
|
module P2P.IO
|
2016-11-30 20:38:16 +00:00
|
|
|
( RunEnv(..)
|
2016-11-20 19:45:01 +00:00
|
|
|
, runNetProtoHandle
|
2016-11-30 20:38:16 +00:00
|
|
|
, runNetHandle
|
2016-11-20 16:08:16 +00:00
|
|
|
) where
|
|
|
|
|
2016-11-24 20:36:16 +00:00
|
|
|
import P2P.Protocol
|
2016-11-20 16:08:16 +00:00
|
|
|
import Utility.Process
|
|
|
|
import Git
|
|
|
|
import Git.Command
|
2016-11-30 20:38:16 +00:00
|
|
|
import Utility.AuthToken
|
2016-11-20 16:08:16 +00:00
|
|
|
import Utility.SafeCommand
|
|
|
|
import Utility.SimpleProtocol
|
2016-11-21 21:27:38 +00:00
|
|
|
import Utility.Exception
|
2016-11-20 16:08:16 +00:00
|
|
|
|
|
|
|
import Control.Monad
|
|
|
|
import Control.Monad.Free
|
|
|
|
import Control.Monad.IO.Class
|
|
|
|
import System.Exit (ExitCode(..))
|
|
|
|
import System.IO
|
|
|
|
import Control.Concurrent
|
2016-11-21 23:24:55 +00:00
|
|
|
import Control.Concurrent.Async
|
2016-11-20 16:08:16 +00:00
|
|
|
import qualified Data.ByteString as B
|
|
|
|
import qualified Data.ByteString.Lazy as L
|
|
|
|
|
2016-12-01 03:54:00 +00:00
|
|
|
-- Type of interpreters of the Proto free monad.
|
2016-11-22 01:22:58 +00:00
|
|
|
type RunProto = forall a m. (MonadIO m, MonadMask m) => Proto a -> m (Maybe a)
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-30 20:38:16 +00:00
|
|
|
data RunEnv = RunEnv
|
|
|
|
{ runRepo :: Repo
|
|
|
|
, runCheckAuth :: (AuthToken -> Bool)
|
|
|
|
, runIhdl :: Handle
|
|
|
|
, runOhdl :: Handle
|
2016-11-20 16:08:16 +00:00
|
|
|
}
|
|
|
|
|
2016-12-01 03:54:00 +00:00
|
|
|
-- Interpreter of Proto that communicates with a peer over a Handle.
|
|
|
|
--
|
|
|
|
-- No Local actions will be run; if the interpreter reaches any,
|
|
|
|
-- it returns Nothing.
|
2016-11-30 20:38:16 +00:00
|
|
|
runNetProtoHandle :: (MonadIO m, MonadMask m) => RunEnv -> Proto a -> m (Maybe a)
|
|
|
|
runNetProtoHandle runenv = go
|
2016-11-20 16:08:16 +00:00
|
|
|
where
|
|
|
|
go :: RunProto
|
2016-11-22 01:22:58 +00:00
|
|
|
go (Pure v) = pure (Just v)
|
2016-11-30 20:38:16 +00:00
|
|
|
go (Free (Net n)) = runNetHandle runenv go n
|
2016-11-22 01:22:58 +00:00
|
|
|
go (Free (Local _)) = return Nothing
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-12-01 03:54:00 +00:00
|
|
|
-- Interprater of Net that communicates with a peer over a Handle.
|
|
|
|
--
|
|
|
|
-- An interpreter for Proto has to be provided.
|
2016-11-30 20:38:16 +00:00
|
|
|
runNetHandle :: (MonadIO m, MonadMask m) => RunEnv -> RunProto -> NetF (Proto a) -> m (Maybe a)
|
|
|
|
runNetHandle runenv runner f = case f of
|
2016-11-20 16:08:16 +00:00
|
|
|
SendMessage m next -> do
|
2016-11-22 01:22:58 +00:00
|
|
|
v <- liftIO $ tryIO $ do
|
2016-11-30 20:38:16 +00:00
|
|
|
hPutStrLn (runOhdl runenv) (unwords (formatMessage m))
|
|
|
|
hFlush (runOhdl runenv)
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
|
|
|
Left _e -> return Nothing
|
|
|
|
Right () -> runner next
|
2016-11-20 16:08:16 +00:00
|
|
|
ReceiveMessage next -> do
|
2016-11-30 20:38:16 +00:00
|
|
|
v <- liftIO $ tryIO $ hGetLine (runIhdl runenv)
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
|
|
|
Left _e -> return Nothing
|
|
|
|
Right l -> case parseMessage l of
|
|
|
|
Just m -> runner (next m)
|
|
|
|
Nothing -> runner $ do
|
|
|
|
let e = ERROR $ "protocol parse error: " ++ show l
|
|
|
|
net $ sendMessage e
|
|
|
|
next e
|
2016-11-20 16:08:16 +00:00
|
|
|
SendBytes _len b next -> do
|
2016-11-22 01:22:58 +00:00
|
|
|
v <- liftIO $ tryIO $ do
|
2016-11-30 20:38:16 +00:00
|
|
|
L.hPut (runOhdl runenv) b
|
|
|
|
hFlush (runOhdl runenv)
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
|
|
|
Left _e -> return Nothing
|
|
|
|
Right () -> runner next
|
2016-11-20 16:08:16 +00:00
|
|
|
ReceiveBytes (Len n) next -> do
|
2016-11-30 20:38:16 +00:00
|
|
|
v <- liftIO $ tryIO $ L.hGet (runIhdl runenv) (fromIntegral n)
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
|
|
|
Left _e -> return Nothing
|
|
|
|
Right b -> runner (next b)
|
2016-11-30 20:38:16 +00:00
|
|
|
CheckAuthToken _u t next -> do
|
|
|
|
let authed = runCheckAuth runenv t
|
2016-11-20 20:42:18 +00:00
|
|
|
runner (next authed)
|
2016-11-22 01:22:58 +00:00
|
|
|
Relay hin hout next -> do
|
|
|
|
v <- liftIO $ runRelay runner hin hout
|
|
|
|
case v of
|
|
|
|
Nothing -> return Nothing
|
|
|
|
Just exitcode -> runner (next exitcode)
|
|
|
|
RelayService service next -> do
|
2016-11-30 20:38:16 +00:00
|
|
|
v <- liftIO $ runRelayService runenv runner service
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
|
|
|
Nothing -> return Nothing
|
|
|
|
Just () -> runner next
|
|
|
|
|
|
|
|
runRelay :: RunProto -> RelayHandle -> RelayHandle -> IO (Maybe ExitCode)
|
|
|
|
runRelay runner (RelayHandle hout) (RelayHandle hin) = bracket setup cleanup go
|
2016-11-20 16:08:16 +00:00
|
|
|
where
|
2016-11-21 23:24:55 +00:00
|
|
|
setup = do
|
|
|
|
v <- newEmptyMVar
|
2016-11-22 01:22:58 +00:00
|
|
|
void $ async $ relayFeeder runner v
|
|
|
|
void $ async $ relayReader v hout
|
2016-11-21 23:24:55 +00:00
|
|
|
return v
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-21 23:24:55 +00:00
|
|
|
cleanup _ = do
|
|
|
|
hClose hin
|
|
|
|
hClose hout
|
|
|
|
|
|
|
|
go v = relayHelper runner v hin
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-30 20:38:16 +00:00
|
|
|
runRelayService :: RunEnv -> RunProto -> Service -> IO (Maybe ())
|
|
|
|
runRelayService runenv runner service = bracket setup cleanup go
|
2016-11-20 16:08:16 +00:00
|
|
|
where
|
|
|
|
cmd = case service of
|
|
|
|
UploadPack -> "upload-pack"
|
|
|
|
ReceivePack -> "receive-pack"
|
2016-11-21 21:27:38 +00:00
|
|
|
|
|
|
|
serviceproc = gitCreateProcess
|
|
|
|
[ Param cmd
|
2016-11-30 20:38:16 +00:00
|
|
|
, File (repoPath (runRepo runenv))
|
|
|
|
] (runRepo runenv)
|
2016-11-21 21:27:38 +00:00
|
|
|
|
|
|
|
setup = do
|
2016-11-21 23:24:55 +00:00
|
|
|
(Just hin, Just hout, _, pid) <- createProcess serviceproc
|
|
|
|
{ std_out = CreatePipe
|
|
|
|
, std_in = CreatePipe
|
|
|
|
}
|
|
|
|
v <- newEmptyMVar
|
2016-11-22 01:22:58 +00:00
|
|
|
void $ async $ relayFeeder runner v
|
|
|
|
void $ async $ relayReader v hout
|
2016-11-21 23:24:55 +00:00
|
|
|
waiter <- async $ waitexit v pid
|
2016-11-22 01:22:58 +00:00
|
|
|
return (v, waiter, hin, hout, pid)
|
2016-11-21 23:24:55 +00:00
|
|
|
|
2016-11-22 01:22:58 +00:00
|
|
|
cleanup (_, waiter, hin, hout, pid) = do
|
2016-11-21 21:27:38 +00:00
|
|
|
hClose hin
|
|
|
|
hClose hout
|
2016-11-21 23:24:55 +00:00
|
|
|
cancel waiter
|
2016-11-21 21:27:38 +00:00
|
|
|
void $ waitForProcess pid
|
|
|
|
|
2016-11-22 01:22:58 +00:00
|
|
|
go (v, _, hin, _, _) = do
|
|
|
|
r <- relayHelper runner v hin
|
|
|
|
case r of
|
|
|
|
Nothing -> return Nothing
|
|
|
|
Just exitcode -> runner $ net $ relayToPeer (RelayDone exitcode)
|
2016-11-21 23:24:55 +00:00
|
|
|
|
|
|
|
waitexit v pid = putMVar v . RelayDone =<< waitForProcess pid
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-21 23:24:55 +00:00
|
|
|
-- Processes RelayData as it is put into the MVar.
|
2016-11-22 01:22:58 +00:00
|
|
|
relayHelper :: RunProto -> MVar RelayData -> Handle -> IO (Maybe ExitCode)
|
2016-11-21 23:24:55 +00:00
|
|
|
relayHelper runner v hin = loop
|
|
|
|
where
|
|
|
|
loop = do
|
2016-11-20 16:08:16 +00:00
|
|
|
d <- takeMVar v
|
|
|
|
case d of
|
2016-11-21 23:24:55 +00:00
|
|
|
RelayFromPeer b -> do
|
|
|
|
L.hPut hin b
|
|
|
|
hFlush hin
|
|
|
|
loop
|
|
|
|
RelayToPeer b -> do
|
2016-11-22 01:22:58 +00:00
|
|
|
r <- runner $ net $ relayToPeer (RelayToPeer b)
|
|
|
|
case r of
|
|
|
|
Nothing -> return Nothing
|
|
|
|
Just () -> loop
|
2016-11-21 23:24:55 +00:00
|
|
|
RelayDone exitcode -> do
|
2016-11-22 01:22:58 +00:00
|
|
|
_ <- runner $ net $ relayToPeer (RelayDone exitcode)
|
|
|
|
return (Just exitcode)
|
2016-11-21 23:24:55 +00:00
|
|
|
|
|
|
|
-- Takes input from the peer, and puts it into the MVar for processing.
|
2016-11-22 01:45:56 +00:00
|
|
|
-- Repeats until the peer tells it it's done or hangs up.
|
2016-11-21 23:24:55 +00:00
|
|
|
relayFeeder :: RunProto -> MVar RelayData -> IO ()
|
|
|
|
relayFeeder runner v = loop
|
|
|
|
where
|
|
|
|
loop = do
|
2016-11-22 01:22:58 +00:00
|
|
|
mrd <- runner $ net relayFromPeer
|
|
|
|
case mrd of
|
2016-11-22 01:45:56 +00:00
|
|
|
Nothing -> putMVar v (RelayDone (ExitFailure 1))
|
2016-11-22 01:22:58 +00:00
|
|
|
Just rd -> do
|
|
|
|
putMVar v rd
|
|
|
|
case rd of
|
|
|
|
RelayDone _ -> return ()
|
|
|
|
_ -> loop
|
2016-11-21 23:24:55 +00:00
|
|
|
|
|
|
|
-- Reads input from the Handle and puts it into the MVar for relaying to
|
|
|
|
-- the peer. Continues until EOF on the Handle.
|
|
|
|
relayReader :: MVar RelayData -> Handle -> IO ()
|
|
|
|
relayReader v hout = loop
|
|
|
|
where
|
|
|
|
loop = do
|
2016-11-22 00:56:58 +00:00
|
|
|
bs <- getsome []
|
|
|
|
case bs of
|
|
|
|
[] -> return ()
|
|
|
|
_ -> do
|
|
|
|
putMVar v $ RelayToPeer (L.fromChunks bs)
|
2016-11-21 23:24:55 +00:00
|
|
|
loop
|
2016-11-22 00:56:58 +00:00
|
|
|
|
|
|
|
-- Waiit for the first available chunk. Then, without blocking,
|
|
|
|
-- try to get more chunks, in case a stream of chunks is being
|
|
|
|
-- written in close succession.
|
|
|
|
--
|
|
|
|
-- On Windows, hGetNonBlocking is broken, so avoid using it there.
|
|
|
|
getsome [] = do
|
|
|
|
b <- B.hGetSome hout chunk
|
|
|
|
if B.null b
|
|
|
|
then return []
|
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
else getsome [b]
|
|
|
|
#else
|
|
|
|
else return [b]
|
|
|
|
#endif
|
|
|
|
getsome bs = do
|
|
|
|
b <- B.hGetNonBlocking hout chunk
|
|
|
|
if B.null b
|
|
|
|
then return (reverse bs)
|
|
|
|
else getsome (b:bs)
|
|
|
|
|
|
|
|
chunk = 65536
|