packages feed

ide-backend-server-0.10.0: Posix.hsc

{-

Modified by Michael Sloan, FP Complete, in 2015

Copyright 2012-2013 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-}

{-# LANGUAGE ForeignFunctionInterface #-}
{-# OPTIONS_GHC -fno-warn-unused-imports #-}

-- | Modified by Michael Sloan, FP Complete, in 2015
--
-- Based on https://github.com/mzero/plush/blob/ffa6c84f7bcb7bd57079e9a519e2bdc1ae07b4be/src/System/Posix/Missing.hsc
module Posix (
    dupFd, dupFdCloseOnExec,

    loginTerminal,
    setControllingTerminal,

    getArg0,
    executeFile0,
    -- * From Plush.Run.Posix.IO
    write,
    readChunk
    ) where

import           Control.Applicative ((<$>))
import           Control.Arrow (second)
import           Control.Exception (throwIO)
import           Control.Monad (when, void)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Internal as BS (createAndTrim)
import           Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import           Foreign (withArray0, withMany)
import Foreign.C (CInt(..), CString, CULong(..),
    throwErrnoIfMinus1, throwErrnoIfMinus1_, throwErrnoPathIfMinus1_)
import           Foreign.Ptr (Ptr, nullPtr, castPtr, plusPtr)
import           Foreign.Ptr (nullPtr, Ptr)
import           Foreign.Safe (alloca, peek, peekElemOff)
import           GHC.Foreign (peekCString)
import           GHC.IO.Device (SeekMode(SeekFromEnd, AbsoluteSeek))
import           GHC.IO.Encoding (getFileSystemEncoding)
import           GHC.IO.Exception (IOErrorType(UnsupportedOperation))
import           System.IO.Error (ioeGetErrorType, catchIOError)
import           System.Posix.IO (fdReadBuf, fdSeek, fdWriteBuf, setFdOption, FdOption(CloseOnExec))
import           System.Posix.Internals (c_fcntl_write, FD, withFilePath)
import           System.Posix.Process.Internals (pPrPr_disableITimers)
import           System.Posix.Types (Fd(Fd))

#include "HsUnix.h"

-- | This is like 'dup', but takes an extra argument. The new file descriptor
-- will be the lowest free file descriptor not less than the second argument.
-- The 'CloseOnExec' flag (FD_CLOEXEC) will be cleared on the new descriptor.
dupFd :: Fd -> Fd -> IO Fd
dupFd = dupFdViaFcntl (#const F_DUPFD)

-- | Same as 'dupFd', but the 'CloseOnExec' flag (FD_CLOEXEC) will be set.
dupFdCloseOnExec :: Fd -> Fd -> IO Fd
-- dupFdCloseOnExec = dupFdViaFcntl (#const F_DUPFD_CLOEXEC)
    -- While this is POSIX, is is relatively new, and many systems do not
    -- support this operation. The following code simulates it.
dupFdCloseOnExec src base = do
    dest <- dupFd src base
    setFdOption dest CloseOnExec True
    return dest

dupFdViaFcntl :: CInt -> Fd -> Fd -> IO Fd
dupFdViaFcntl op (Fd srcFd) (Fd minFd) = Fd `fmap`
  throwErrnoIfMinus1 "dupFdViaFcntl"
                      (c_fcntl_write srcFd op (fromIntegral minFd))

-- | Prepares for login on the tty, which should be a real tty or the slave
-- from a call to `openPseudoTerminal`. A new session will be started, the tty
-- will become the controlling terminal, and the tty is dup'd down to become
-- the standard in, out and err FDs.
loginTerminal :: Fd -> IO ()

#ifdef HAVE_OPENPTY
    -- In theory this should be testing HAVE_LOGIN_TTY, but that doesn't exist.
    -- The calls login_tty, openpty, and forkpty are part of the same BSD module
    -- and so are all generally present together or not.
loginTerminal (Fd ttyFd) =
    throwErrnoIfMinus1_ "loginTerminal" (c_login_tty ttyFd)

foreign import ccall unsafe "login_tty"
  c_login_tty :: CInt -> IO CInt

#else
loginTerminal tty = do
    _ <- createSession
    setControllingTerminal tty
    _ <- dupTo tty stdInput
    _ <- dupTo tty stdOutput
    _ <- dupTo tty stdError
    when (tty > stdError) $ closeFd tty
#endif

-- | Set the controlling terminal of the process. The tty should be a real tty
-- or the slave from a call to 'openPseudoTerminal'.
setControllingTerminal :: Fd -> IO ()

#ifdef TIOCSCTTY
setControllingTerminal (Fd ttyfd) =
    throwErrnoIfMinus1_ "setControllingTerminal"
        (ioctl ttyfd (#const TIOCSCTTY) nullPtr)

foreign import ccall ioctl :: FD -> CULong -> Ptr a -> IO CInt
#else
-- POSIX doesn't spec how to set the controlling terminal! The ioctl TIOSCTTY
-- method above is generally available, but some systems don't have it. The
-- second most common way to set the controlling terminal is to open it when no
-- other terminal is opened in the process. That is what this alternative
-- implementation does. Be forewarned that they may be systems that use yet some
-- third unknown method.
setControllingTerminal tty = do
    ttyName <- getTerminalName tty
    when (tty /= stdInput) $ closeFd stdInput
    when (tty /= stdOutput) $ closeFd stdOutput
    when (tty /= stdError) $ closeFd stdError
    openFd ttyName ReadWrite Nothing defaultFileFlags >>= closeFd
#endif

-- | Return @argv[0]@ as passed to the program on invocation. Unlike
-- 'getProgName', this is the raw value passed to the process. It may, or may
-- not, have anything to do with the name of the running executable!
getArg0 :: IO String
getArg0 =
    alloca $ \ p_argc ->
    alloca $ \ p_argv -> do
        getProgArgv p_argc p_argv
        argv <- peek p_argv
        enc <- getFileSystemEncoding
        peekElemOff argv 0 >>= peekCString enc

foreign import ccall unsafe "getProgArgv"
  getProgArgv :: Ptr CInt -> Ptr (Ptr CString) -> IO ()


-- | Like 'executeFile', but allow for expressly setting @argv[0]@
-- For simplicity, this version has no path search option, and always takes an
-- environment.  If incorporating into System.Posix.Process, the signature
-- should better match 'executeFile'.
executeFile0 :: FilePath -> String -> [String] -> [(String, String)] -> IO a
executeFile0 path cmd args env = do
  withFilePath path $ \s ->
    withMany withFilePath (cmd:args) $ \cstrs ->
      withArray0 nullPtr cstrs $ \arg_arr ->
    let env' = map (\ (name, val) -> name ++ ('=' : val)) env in
    withMany withFilePath env' $ \cenv ->
      withArray0 nullPtr cenv $ \env_arr -> do
    pPrPr_disableITimers
    throwErrnoPathIfMinus1_ "executeFile" path
        (c_execve s arg_arr env_arr)
    return undefined -- never reached

foreign import ccall unsafe "execve"
  c_execve :: CString -> Ptr CString -> Ptr CString -> IO CInt

-- Copied from Plush.Run.Posix
-- https://github.com/mzero/plush/blob/ffa6c84f7bcb7bd57079e9a519e2bdc1ae07b4be/src/Plush/Run/Posix.hs

-- | Based on code for readLoop, but instead expects a blocking Fd.
readChunk :: Fd -> IO BS.ByteString
readChunk fd =
    BS.createAndTrim bufSize $ \buf ->
        fmap fromIntegral (fdReadBuf fd buf (fromIntegral bufSize))
  where
    bufSize = 4096

-- | 'write' for 'IO': Seek to the end, and write.
--
-- Modified to take a strict bytestring
write :: Fd -> BS.ByteString -> IO ()
write fd bs = unsafeUseAsCStringLen bs writeBuf
  where
    writeBuf (p, n) | n > 0 = do
        ignoreUnsupportedOperation $ fdSeek fd SeekFromEnd 0
        m <- fromIntegral `fmap` fdWriteBuf fd (castPtr p) (fromIntegral n)
        when (0 <= m && m <= n) $ writeBuf (p `plusPtr` m, n - m)
    writeBuf _ = return ()

ignoreUnsupportedOperation :: IO a -> IO ()
ignoreUnsupportedOperation act = void act `catchIOError` \ex ->
    if ioeGetErrorType ex == UnsupportedOperation
        then return ()
        else throwIO ex