Golang FIFO and named pipes: mkfifo, syscall.Mkfifo, EOF, open modes

Golang fifo and golang mkfifo: create fifo files with syscall.Mkfifo or mkfifo command in linux, golang named pipe reader and writer, O_RDONLY vs O_RDWR and EOF handling—linux fifo example and go fifo inter-process communication.

Published

Updated

Read time 3 min read

Reviewed byDeepak Prasad

Golang FIFO and named pipes: mkfifo, syscall.Mkfifo, EOF, open modes

A golang fifo (also called a golang named pipe or fifo file) lets two programs—or two goroutines—stream bytes in one direction through a special file on disk. Queries such as golang mkfifo, mkfifo example, linux fifo example, linux mkfifo example, mkfifo command in linux, or go fifo usually map to either the shell mkfifo utility or syscall.Mkfifo in Go. This tutorial shows both ways to create the node, a complete reader and writer in one program, and how O_RDONLY versus O_RDWR changes EOF behavior. You should be comfortable with goroutines and basic file I/O before trying fifos.

Tested with Go 1.24 on Linux.


Create a fifo file: mkfifo command in Linux and syscall.Mkfifo

Linux mkfifo example (shell):

text
mkfifo /tmp/demo.fifo

In another terminal you can cat /tmp/demo.fifo in one window and echo hello > /tmp/demo.fifo in another—the shell example is the quickest fifo files sanity check.

Golang mkfifo uses syscall.Mkfifo (Linux-oriented; named pipes differ on Windows):

go
if err := syscall.Mkfifo("/tmp/demo.fifo", 0o600); err != nil {
	panic(err)
}

Use a path under /tmp for experiments and remove the node when you are finished.


Reader and writer (go fifo) with O_RDONLY

Opening a fifo for read usually blocks until a writer opens it for write (and the opposite is also true). The program below creates the fifo, starts a writer goroutine that opens write-only, then opens read-only in main and prints lines until EOF after the writer closes. It needs a real Linux fifo on disk, so save it as a .go file and run go run locally (not in the in-browser Run environment).

go
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"
	"syscall"
	"time"
)

const fifoPath = "/tmp/gocheckfifo"

func main() {
	_ = os.Remove(fifoPath)
	if err := syscall.Mkfifo(fifoPath, 0o600); err != nil {
		panic(err)
	}
	defer os.Remove(fifoPath)

	go func() {
		time.Sleep(200 * time.Millisecond)
		w, err := os.OpenFile(fifoPath, os.O_WRONLY, 0)
		if err != nil {
			panic(err)
		}
		defer w.Close()
		for i := 0; i < 3; i++ {
			if _, err := fmt.Fprintf(w, "line%d\n", i); err != nil {
				panic(err)
			}
			time.Sleep(50 * time.Millisecond)
		}
	}()

	r, err := os.OpenFile(fifoPath, os.O_RDONLY, 0)
	if err != nil {
		panic(err)
	}
	defer r.Close()

	br := bufio.NewReader(r)
	for {
		line, err := br.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				if len(line) > 0 {
					fmt.Println("reader:", strings.TrimSpace(string(line)))
				}
				fmt.Println("reader: EOF")
				break
			}
			panic(err)
		}
		fmt.Println("reader:", strings.TrimSpace(string(line)))
	}
}

Example output from go run on Linux:

text
reader: line0
reader: line1
reader: line2
reader: EOF

O_RDONLY vs O_RDWR on a fifo

With os.O_RDONLY, when the last writer closes, the reader drains any buffered bytes and then observes io.EOF, which is what most linux fifo example readers expect.

Opening the same fifo with os.O_RDWR is sometimes used so OpenFile does not block waiting for a peer, but EOF semantics change because the fifo can remain logically open; you often combine RDWR with explicit framing, context timeouts, or shutting down the reader when your protocol ends. Avoid os.Exit in libraries because it skips deferred cleanup.


Summary

Golang fifo and golang named pipe workflows come down to creating a fifo node (mkfifo or syscall.Mkfifo), pairing a writer (O_WRONLY) with a reader (O_RDONLY), and handling io.EOF when writers finish. Golang mkfifo in code creates the same kind of fifo file as the mkfifo command in linux on the filesystem. Pick O_RDWR only when you understand the blocking and EOF trade-offs. For broader concurrency patterns, keep goroutines and Go channels in mind alongside fifos.


References


Frequently Asked Questions

1. What is a golang fifo or golang named pipe?

A FIFO is a special file (named pipe) that connects a writer and a reader; bytes written at one end are read first-in first-out at the other, often across two processes or a goroutine and the main program.

2. How do I golang mkfifo create a fifo file?

On Linux use syscall.Mkfifo(path, mode) from the syscall package, or create the node with the mkfifo shell command; both produce a fifo file on disk that programs open like a regular file.

3. Why does opening a fifo block?

Opening a fifo for read typically blocks until another process opens it for write (and vice versa) so the kernel can connect both ends; use goroutines or separate terminals like a linux fifo example with two shells.

4. When do I see EOF on a fifo reader?

With O_RDONLY, when all writers close their file descriptors the reader eventually reads remaining bytes then io.EOF; with O_RDWR the kernel may keep the fifo open so EOF behavior differs and timeouts or explicit protocol are often used instead.

5. Is syscall.Mkfifo portable to Windows?

Named pipes on Windows differ from Linux fifos; this article targets Linux; use OS-specific APIs or conditional build tags when you need portable named pipes.
Antony Shikubu

Systems Integration Engineer

Highly skilled software developer with expertise in Python, Golang, and AWS cloud services.