Nathan Grigg

Fun with FIFOs (and Minecraft)

Here is a little test you can do to play around with FIFOs (also known as Named Pipes) in bash.

For setup, we run

mkfifo test

which creates a FIFO named test in the current directory.

Then run

cat - < test

This opens test for reading and connects it to stdin, then runs cat -, which copies stdin to stdout. Nothing will happen (for now) because there are no writers for test.

In a separate window, execute

echo "hi" > test

You will see your first window print “hi” and then the cat command will exit.

The reason that cat exits is because as soon as the echo command finishes, there are no writers connected to test. In that case, anyone who tries to read from the pipe will get an EOF, which is what cat uses to know it should move on to the next file or exit. You can see this in action by running

sleep 60 > test &

Now you can run as many “echo” commands as you want (for 60 seconds) and cat will keep running.

The reason that cat doesn’t just return before the first echo (after all, there are no writers at the beginning) is that Bash tells the OS to wait for a writer to connect before executing its command. You can see this by replacing cat - with

{ echo "starting"; cat - } < test

You will see that “starting” is not written to stdout until the first writer is connected (either the first echo or the sleep command).

Read-write file descriptor

A better way than sleep to keep a writer open is to use a read-write file descriptor:

cat - 3<> test < test

This starts by opening test for reading and writing and connecting it to file descriptor 3. Then it also opens it for reading and connects it to stdin.

We won’t actually use file descriptor 3 to pass data, but the <> operator is nonblocking (so it doesn’t matter that there are no other readers or writers yet). It remains open for the life of the cat command, so cat will start immediately and never see EOF on its stdin.

Minecraft

I started on this investigation while trying to run a persistent Minecraft server for my kids. Minecraft works by writing logs to stdout and reading commands from stdin.

My runit script to keep this running in the background looks like this (omitting some details such as error handling and the java options):

#!/bin/sh

cd /var/opt/minecraft
mkfifo fifo

exec chpst -u minecraft java \
    -jar /opt/minecraft/server.jar \
    nogui 3<> fifo < fifo

Then if I need to send a command to the running server, I can run, for example,

echo "op user0293" | sudo tee /var/opt/minecraft/fifo