{-# OPTIONS_GHC -fno-warn-orphans #-} {-| POSIX calls and emulations. > And now all those lands lie under the wave. > And I walk in Ambarona, in Tauremorna, in Aldalome. > In my own land, in the country of Fangorn, > Where the roots are long, > And the years lie thicker than the leaves > In Tauremornalome. -} module Pugs.Compat.Posix ( _PUGS_HAVE_POSIX, createLink, createSymbolicLink, readSymbolicLink, rename, removeLink, setFileMode, getEnvironment, getArg0, statFileSize, statFileMTime, statFileCTime, statFileATime, getProcessID, getRealUserID, getEffectiveUserID, getRealGroupID, getEffectiveGroupID, getProcessTimes, setEnv, getEnv, unsetEnv, signalProcess, ProcessTimes(..), clocksPerSecond, DirStream, openDirStream, readDirStream, rewindDirStream, closeDirStream, executeFile', -- the prime signifies we changed signature. getCurrentDirectory, setCurrentDirectory, doesFileExist, doesDirectoryExist, doesExist, pugsTimeSpec ) where import Foreign import System.Cmd import System.Posix.Types import Data.Time #ifdef PUGS_HAVE_POSIX import System.Posix.Files import System.Posix.Process import System.Posix.Env hiding (getEnvironment) import System.Posix.Directory import System.Posix.User import System.Exit import System.Environment (getEnvironment) import Foreign.C.Types import Foreign.C.String import Data.Typeable import qualified System.Posix.Signals _PUGS_HAVE_POSIX :: Bool _PUGS_HAVE_POSIX = True doesExist :: FilePath -> IO Bool doesExist = fileExist testStatusWith :: (FileStatus -> Bool) -> FilePath -> IO Bool testStatusWith t f = fmap t (getFileStatus f) `catch` const (return False) doesFileExist :: FilePath -> IO Bool doesFileExist = testStatusWith isRegularFile doesDirectoryExist :: FilePath -> IO Bool doesDirectoryExist = testStatusWith isDirectory getCurrentDirectory :: IO FilePath getCurrentDirectory = getWorkingDirectory setCurrentDirectory :: FilePath -> IO () setCurrentDirectory = changeWorkingDirectory executeFile' :: FilePath -> Bool -> [String] -> Maybe [(String, String)] -> IO ExitCode executeFile' prog True args Nothing = rawSystem prog args executeFile' prog search args env = do print (prog, search, args, env) executeFile prog search args env return $ ExitFailure 1 -- if we got here, it failed. statFileSize :: FilePath -> IO Integer statFileSize f = do s <- getFileStatus f return (toInteger (fileSize s)) statFileTime :: (FileStatus -> EpochTime) -> FilePath -> IO Integer statFileTime op f = do s <- getFileStatus f return (toInteger $ fromEnum $ op s) statFileMTime :: FilePath -> IO Integer statFileMTime f = statFileTime modificationTime f >>= return statFileCTime :: FilePath -> IO Integer statFileCTime f = statFileTime statusChangeTime f >>= return statFileATime :: FilePath -> IO Integer statFileATime f = statFileTime accessTime f >>= return type Signal = System.Posix.Signals.Signal signalProcess :: Signal -> ProcessID -> IO () signalProcess = System.Posix.Signals.signalProcess clocksPerSecond :: (Num a) => a clocksPerSecond = 1000000 instance Typeable DirStream where typeOf _ = mkTyConApp (mkTyCon "DirStream") [] #else import qualified Control.Exception import Debug.Trace import qualified System.Environment import System.Directory (getCurrentDirectory, setCurrentDirectory, doesFileExist, doesDirectoryExist) import IO import System.IO import System.Exit import Foreign.C.String import Foreign.Ptr import Foreign.C.Types import Data.Ratio import Data.Typeable import System.Posix.Internals import Foreign.C.Error _PUGS_HAVE_POSIX :: Bool _PUGS_HAVE_POSIX = False failWith :: (Monad m) => String -> m a failWith s = fail $ "'" ++ s ++ "' not implemented on this platform." failWithIncomplete :: (Monad m) => String -> m a failWithIncomplete s = fail $ "'" ++ s ++ "' not fully implemented on this platform." warnWith :: String -> IO () warnWith s = trace ("'" ++ s ++ "' not implemented on this platform.") $ return () -- This should all be moved into Compat.Win32, once we go that route -- This is partially right - but some APIs return even "better" resolution clocksPerSecond :: (Num a) => a clocksPerSecond = 1000000 foreign import stdcall unsafe "SetEnvironmentVariableW" win32SetEnv :: CWString -> CWString -> IO () setEnv :: String -> String -> Bool -> IO () setEnv k v _ = withCWString k $ withCWString v . win32SetEnv getEnv :: String -> IO (Maybe String) getEnv k = (fmap Just (System.Environment.getEnv k)) `Control.Exception.catch` (const $ return Nothing) unsetEnv :: String -> IO () unsetEnv k = withCWString k $ \ key -> withCWString "" $ \ v -> do win32SetEnv key v -- #unsetEnv _ = warnWith "unsetEnv" getEnvironment :: IO [(String, String)] getEnvironment = System.Environment.getEnvironment createLink :: FilePath -> FilePath -> IO () createLink _ _ = warnWith "link" createSymbolicLink :: FilePath -> FilePath -> IO () createSymbolicLink _ _ = warnWith "symlink" readSymbolicLink :: FilePath -> IO FilePath readSymbolicLink _ = failWith "readlink" rename :: FilePath -> FilePath -> IO () rename _ _ = warnWith "rename" removeLink :: FilePath -> IO () removeLink _ = warnWith "unlink" setFileMode :: FilePath -> FileMode -> IO () setFileMode _ _ = warnWith "chmod" newtype DirStream = DirStream (Ptr CDir) deriving (Typeable) openDirStream :: FilePath -> IO DirStream openDirStream name = withCString name $ \s -> do dirp <- c_opendir s return (DirStream dirp) readDirStream :: DirStream -> IO FilePath readDirStream (DirStream dirp) = alloca $ \ptr_dEnt -> loop ptr_dEnt where loop ptr_dEnt = do resetErrno r <- readdir dirp ptr_dEnt if (r == 0) then do dEnt <- peek ptr_dEnt if (dEnt == nullPtr) then return [] else do entry <- (d_name dEnt >>= peekCString) freeDirEnt dEnt return entry else do errno <- getErrno if (errno == eINTR) then loop ptr_dEnt else do let (Errno eo) = errno if (eo == end_of_dir) then return [] else throwErrno "readDirStream" rewindDirStream :: DirStream -> IO () rewindDirStream (DirStream dirp) = do c_rewinddir dirp return () closeDirStream :: DirStream -> IO () closeDirStream (DirStream dirp) = do c_closedir dirp return () -- Win32 specific type FILETIME = CULLong -- we'll keep the accuracy as long as possible data ProcessTimes = ProcessTimes { elapsedTime :: Rational , userTime :: Rational , systemTime :: Rational , childUserTime :: Rational , childSystemTime :: Rational } foreign import stdcall unsafe "GetProcessTimes" win32GetProcessTimes :: Int -> Ptr FILETIME -> Ptr FILETIME -> Ptr FILETIME -> Ptr FILETIME -> IO Int -- relies on Int == Int32 on Windows filetimeToSeconds :: FILETIME -> Rational filetimeToSeconds ft = ((toInteger ft) % 10) -- See Perl5 win32/win32.c for the complete implementation -- that works on Windows 95 as well getProcessTimes :: IO ProcessTimes getProcessTimes = do pid <- getProcessHandle alloca $ \ pDummy -> do alloca $ \ pKernel -> do alloca $ \ pUser -> do poke pDummy 0 poke pKernel 0 poke pUser 0 win32GetProcessTimes pid pDummy pDummy pKernel pUser user <- peek pUser kernel <- peek pKernel return $ ProcessTimes 0 (filetimeToSeconds user) (filetimeToSeconds kernel) 0 0 -- This is Win32 specific, dunno about other non POSIX platforms statFileSize :: FilePath -> IO Integer statFileSize n = bracket (openFile n ReadMode) hClose hFileSize -- statFileSize _ = failWith "-s" -- Again, Win32 specific magic, as stolen from GHC foreign import stdcall unsafe "GetCurrentProcessId" getProcessID :: IO Int -- relies on Int == Int32 on Windows foreign import stdcall "GetCurrentProcess" getProcessHandle :: IO Int -- relies on Int == Int32 on Windows -- getProcessID :: IO Int -- getProcessID = return $ 1 type UserID = Int type GroupID = Int getRealUserID :: IO UserID getRealUserID = return 1 getEffectiveUserID :: IO UserID getEffectiveUserID = return 1 getRealGroupID :: IO GroupID getRealGroupID = return 1 getEffectiveGroupID :: IO GroupID getEffectiveGroupID = return 1 signalProcess :: Int -> Int -> IO () signalProcess _ _ = failWith "kill" executeFile' :: FilePath -> Bool -> [String] -> Maybe [(String, String)] -> IO ExitCode executeFile' prog True args Nothing = rawSystem prog args executeFile' _ _ _ _ = failWithIncomplete "executeFile" doesExist :: FilePath -> IO Bool doesExist f = do rv <- doesFileExist f if rv then return rv else doesDirectoryExist f statFileMTime :: FilePath -> IO Integer statFileMTime _ = failWith "-M" statFileCTime :: FilePath -> IO Integer statFileCTime _ = failWith "-C" statFileATime :: FilePath -> IO Integer statFileATime _ = failWith "-A" #endif foreign import ccall unsafe "getProgArgv" getProgArgv :: Ptr CInt -> Ptr (Ptr CString) -> IO () getArg0 :: IO String getArg0 = do alloca $ \ p_argc -> do alloca $ \ p_argv -> do getProgArgv p_argc p_argv argv <- peek p_argv peekCString =<< peekElemOff argv 0 epochY2K :: UTCTime epochY2K = UTCTime { utctDay = fromGregorian 2000 01 01 , utctDayTime = 0 } {-| Convert an internal @ClockTime@ to a Pugs-style fractional time. Used by op0 "time", @Pugs.Run.prepareEnv@, and the file time tests. -} pugsTimeSpec :: UTCTime -> Rational pugsTimeSpec clkt = toRational (diffUTCTime clkt epochY2K)