Issue
This is what I have so far. Currently, it hangs and does nothing. Note that I need to be able to receive data from this program rot-N and send data back at the right time based on what it said. It is necessary for this to happen without any extra funny modules being installed. I just need to know what the magic words are to do this since I have been unable to find them anywhere else.
import Data.Array
import System.Process
import System.IO
import Text.Printf
--definition for the decrypt function goes here (I'm not going to write it out because it's not important)
main = do
--create process with pipes
(Just hin, Just hout, _, ph) <- createProcess (shell "./rot-N"){ std_in = CreatePipe, std_out = CreatePipe }
--discard first line of std_out to the console
putStrLn =<< hGetLine hout
--bind second line of std_out to the name "ciphertext"
ciphertext <- hGetLine hout
--discard third line of std_out
putStrLn =<< hGetLine hout
putStrLn ciphertext
--generate array of decrypted strings
let decryptedStrings = listArray (0,25) $ zipWith decrypt [0..25] $ repeat ciphertext
--print these strings to the console in order
mapM_ (uncurry $ printf "%02d: %s\n") $ assocs decryptedStrings
putStrLn "Select the correct string"
number <- getLine >>= return . (read :: Int)
hPutStrLn hin $ decryptedStrings ! number
hClose hin
--I assume that this makes the program finish whatever it would do normally
--(of course I want all of its std_out to get sent to the console as normal)
waitForProcess ph
hClose hout
Solution
The problem is that rot-N
has a bit of a bug. It doesn't flush output after printing its prompts. When rot-N
is run from the command line, you don't notice this, because when stdout is connected to a terminal, output is by default line buffered, so every time rot-N
writes a full line, it immediately gets output on the console. However, when rot-N
is run through a pipe, the output is "block buffered" and multiple output lines accumulate without being output unless they are explicitly flushed. When rot-N
pauses to wait for input, it still hasn't flushed any of the buffered output. So, both programs get stuck waiting for the other's first line of output.
You can observe the problem with the following C program:
#include <stdio.h>
int main()
{
char buffer[4096];
puts("I don't know how to write C!");
fgets(buffer, sizeof(buffer), stdin);
return 0;
}
If you run this program from the shell directly, you get output before it pauses to wait for your input.
$ ./c_program
I don't know how to write C!
<pauses for input>
If you pipe the output through cat
, it immediately asks for input without printing anything:
$ ./c_program | cat
<pauses for input>
If you enter a line, you'll get the output after the program terminates. (Program termination forces a flush.)
If you fix the C program by flushing the output:
// fix #1
#include <stdio.h>
int main()
{
char buffer[4096];
puts("I don't know how to write C!");
fflush(stdout);
fgets(buffer, sizeof(buffer), stdin);
return 0;
}
or by changing to line buffering at the start of the program:
// fix #2
#include <stdio.h>
int main()
{
char buffer[4096];
setlinebuf(stdout);
puts("I don't know how to write C!");
fgets(buffer, sizeof(buffer), stdin);
return 0;
}
then it will work even when run as ./c_program | cat
.
Now, since rot-N
was probably only intended to be run interactively, the author probably won't consider this a real bug. Assuming they refuse to fix their program, the easiest way to work around this is to use the utility program stdbuf
which should be available on any Linux installation. By running:
stdbuf -oL ./name_of_broken_program
this will try to modify the buffering of the program's stdout so it's line buffered. Note that this only works with programs that use the C library I/O functions. So, it should work on C and C++ programs, but it notably doesn't work on Haskell programs.
Anyway, assuming rot-N
is a C program or similar, you'll want:
(Just hin, Just hout, _, ph) <-
createProcess (shell "stdbuf -oL ./rot-N") { std_in = CreatePipe, std_out = CreatePipe }
Note that you almost introduced a similar bug into your own program. You've written:
hPutStrLn hin $ str
but hin
is block buffered, so this statement won't actually cause output to be sent to the rot-N
process until the next flush. It would be best practice to either set hin
to line buffering after the createProcess
call:
hSetBuffering hin LineBuffering
or else call hFlush
after the hPutStrLn hin
call:
hPutStrLn hin $ str
hFlush hin
Fortunately, in your program you hClose hin
after sending the str
which does an implicit flush.
As a final note, if you want the trailing output from rot-N
to go to the console, you'll need to copy it there. The following modified version of your program will probably work:
import Data.Array
import System.Process
import System.IO
import Text.Printf
-- dummy decrypt function
decrypt :: Int -> String -> String
decrypt i str = "encryption " ++ show i ++ " of " ++ str
main = do
-- change #1: use stdbuf utility to set line buffering
(Just hin, Just hout, _, ph) <-
createProcess (shell "stdbuf -oL ./rot-N")
{ std_in = CreatePipe, std_out = CreatePipe }
putStrLn =<< hGetLine hout
ciphertext <- hGetLine hout
putStrLn =<< hGetLine hout
putStrLn ciphertext
let decryptedStrings = listArray (0::Int,25) $ zipWith decrypt [0..25] $ repeat ciphertext
mapM_ (uncurry $ printf "%02d: %s\n") $ assocs decryptedStrings
putStrLn "Select the correct string"
-- change #2: it's polite to flush our own prompts in
-- case someone wants to use *our* program via pipes
hFlush stdout
-- change #3: your version caused a type error. Try this one.
number <- read <$> getLine
hPutStrLn hin $ decryptedStrings ! number
-- note: if you decide you need to delay the `hClose in` for
-- some reason, add an `hFlush hin` to make sure the string is
-- sent to the rot-N process
hClose hin
-- change #4: copy all remaining output
putStr =<< hGetContents hout
waitForProcess ph
hClose hout
Answered By - K. A. Buhr Answer Checked By - Cary Denson (WPSolving Admin)