Bug in IO libraries when sending data through a pipe?

Volker Wysk post at volker-wysk.de
Thu Mar 14 14:20:29 EST 2002


On Mon, 11 Mar 2002, Simon Marlow wrote:

> > There seems to be a bug in the IO libraries. I'm using the following
> > procedure to call an external program and send it data through a pipe.
>
> Could you send us a complete example that we can run to reproduce the
> problem?

I've stripped down my program to produce an example. In the process, the
problem disappeard a few times. I hope it shows up on your machine. The
attached files reproduce it on my machine, but the exact results vary
from run to run.

Volker
-------------- next part --------------
import System
import List
import Posix
import PosixUtil
import IO


main = do
    qinh <- getContents
    let pfade = zeilen qinh
    mapM_ (\pfad -> run "/bin/echo" [pfad]) pfade
    hPutStrLn stderr ("received: " ++ show (length qinh))


run :: FilePath                    -- Command
    -> [String]                    -- Arguments
    -> IO ()
run prog par =
    callIO (\ps -> "Kommando fehlgeschlagen mit " ++ show ps ++ ":\n" ++ kommando prog par)
           (executeFile' prog True par Nothing)


-- Die gegebene Aktion als neuen Proze? ausf?hren. Kind wegforken und auf
-- dessen Beendigung warten. Sein Ergebnis ?berwachen und bei Fehler mit
-- Meldung abbrechen. Der erste Parameter ist eine Funktion, die aus einem
-- ExitStatus eine Fehlermeldung generiert. Sie wird nur mit (Exited
-- (ExitFailure _)) oder (Terminated _) aufgerufen.
callIO :: (ProcessStatus -> String)               -- Fehlermeldung erzeugen
       -> IO ()                                   -- Kindproze?
       -> IO ()
callIO fm io = do
    maybepid <- forkProcess
    case maybepid of
       Nothing ->                                 -- Kind
           io >> exitWith ExitSuccess
       Just pid -> do                             -- Vater
           (Just ps) <- getProcessStatus True True pid   -- auf angehaltenes Kind warten
           if ps == Exited ExitSuccess
               then return ()
               else failIO (fm ps)


zeilen :: String -> [String]
zeilen txt =
    let gruppen = groupBy (\a b -> (a == '\n') == (b == '\n')) txt
    in  filter (\str -> filter (/= '\n') str /= "") gruppen


kommando :: String -> [String] -> String
kommando k par =
    concat (intersperse " " (map shell_quote (k:par)))




executeFile' :: FilePath                   -- Command
             -> Bool                       -- Search PATH?
             -> [String]                   -- Arguments
             -> Maybe [(String, String)]   -- Environment
             -> IO a
executeFile' cmd args env ca = do
    executeFile cmd args env ca
    hPutStrLn stderr ("Kommando " ++ cmd ++ " nicht gefunden")
    exitFailure




failIO :: String -> IO a
failIO meld = do
   hPutStrLn stderr meld
   exitFailure


shell_quote :: String -> String
shell_quote txt =
   let need_to_quote c = c `elem` "' \t\n\"\\|&;()<>!{}*[?]^$`#"
       quote (z:zs) =
          if (z `elem` "\"$`\\") then ('\\':(z:(quote zs)))
                                 else (z:(quote zs))
       quote "" = "\""
   in if any need_to_quote txt
         then '"' : quote txt
         else txt
-------------- next part --------------
0 :: parent child
	./parent > /dev/null

parent : parent.hs
	ghc -O -o parent parent.hs  -package posix -package util -package std -syslib lang

child : child.hs
	ghc -O -o child child.hs  -package posix -package util -package std -syslib lang

clean ::
	rm -f *.o *~ parent child *.hi
-------------- next part --------------
import System
import List
import Posix
import PosixUtil
import IO

main = do
   pipeto senden "./child" []
   hPutStrLn stderr ("sent: " ++ show (length senden))

senden = concat (take 200 (repeat "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"))


pipeto :: String
       -> String
       -> [String]
       -> IO ()
pipeto eing prog par = do
    catch (do (zu, von) <- createPipe	                              -- Pipe von --> zu erzeugen
              vonh <- fdToHandle von                                  -- F?r von brauchen wir einen Handle
              hSetBuffering vonh NoBuffering                          -- nichts zur?ckhalten, wenn der Kindproze? es lesen will
              mpid <- forkProcess                                     -- fork(). Danach hat die Pipe vier offene Enden.
              case mpid of
                 Nothing -> do                                        -- Kind
                    hClose vonh                                       -- Kind-Schreibende schlie?en
                    dupTo zu (intToFd 0)                              -- Kind-Leseende auf die Standardeingabe kopieren
                    fdClose zu                                        -- erstes Kind-Leseende schlie?en
                    executeFile' prog True par Nothing
                 Just pid -> do                                       -- Vater
                    fdClose zu                                        -- Vater-Leseende schlie?en
                    hPutStr vonh eing                                 -- Text druch die R?hre (verz?gert)
                    hClose vonh                                       -- Vater-Schreibende schlie?en
                    (Just ps) <- getProcessStatus True True pid       -- auf angehaltenes Kind warten
                    if ps == Exited ExitSuccess
                        then return ()
                        else failIO ("Kommando fehlgeschlagen mit " ++ show ps ++ ":\n"
                                     ++ kommando prog par)
          )
          (\err -> do errno <- getErrorCode
                      hPutStrLn stderr ("Aufruf fehlgeschlagen: ... | " ++ kommando prog par
                                        ++ "\nerrno = " ++ show errno)
                      ioError err)



executeFile' :: FilePath                   -- Command
             -> Bool                       -- Search PATH?
             -> [String]                   -- Arguments
             -> Maybe [(String, String)]   -- Environment
             -> IO a
executeFile' cmd args env ca = do
    executeFile cmd args env ca
    hPutStrLn stderr ("Kommando " ++ cmd ++ " nicht gefunden")
    exitFailure




failIO :: String -> IO a
failIO meld = do
   hPutStrLn stderr meld
   exitFailure


kommando :: String -> [String] -> String
kommando k par =
    concat (intersperse " " (map shell_quote (k:par)))



shell_quote :: String -> String
shell_quote txt =
   let need_to_quote c = c `elem` "' \t\n\"\\|&;()<>!{}*[?]^$`#"
       quote (z:zs) =
          if (z `elem` "\"$`\\") then ('\\':(z:(quote zs)))
                                 else (z:(quote zs))
       quote "" = "\""
   in if any need_to_quote txt
         then '"' : quote txt
         else txt


More information about the Glasgow-haskell-bugs mailing list