A Gopher's Guide to *NIX Plumbing [workshop remixes!]

feyeleanor 0 views 58 slides Oct 09, 2025
Slide 1
Slide 1 of 58
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58

About This Presentation

A workshop version of my Gopherconf UK talk on accessing posix features from Go, presented at GoLab in October 2025.

Coverage includes process management, pipes, semaphores, syscalls, cgo, and unix domain sockets. All illustrated with full-tested code examples.


Slide Content

A GOPHER'S GUIDE TO
*NIX
ELEANOR M
C
HUGH
@feyeleanor
Workshop Remixes!

2009
2025

The Filthy Lucre Tour

*NIX ARCHITECTURE IN A NUTSHELL
▸kernel manages resources
▸signal is a software interrupt
▸process is a program execution
▸pipe links two processes via stdio
▸socket connects two processes directly
▸hierarchical file system contains inodes
▸inode identifies files and block devices
▸block device is an i/o resource
▸file contains bytes stored on a device
▸shell is an interactive command line

A PRACTICAL DIY PHILOSOPHY
▸a bit anarchic, a bit punk
▸create small tools to solve clearly defined tasks
▸use the dev tools which make sense to you
▸compose these small tools into pipelines
▸input to one task coming from the output of another
▸solve complex problems with reusable components
▸stay flexible

PART 1
THINGS NOT TO DO LOCALLY

github://feyeleanor/2025golab
EXECUTING A COMMAND IN A SUBPROCESS 01.GO
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
c := exec.Command("echo", "Hello, Golang!")
if o, e := c.Output(); e == nil {
fmt.Println("Output:", string(o))
} else {
log.Println("Error:", e)
}
}

github://feyeleanor/2025golab
WRAPPING A SHELL IN A SUBPROCESS 02.GO
package main
import (
"fmt"
"log"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
Parallelize(os.Args[1:], func(s string) {
ShellCommand(s, func(c *exec.Cmd) {
log.Println(
Launch(c, func() {
time.AfterFunc(5*time.Second, func() {
c.Process.Kill()
})
}))
})
})
}
func ShellCommand(s string, f func(*exec.Cmd)) {
c := exec.Command(os.Getenv("SHELL"), "-c", s)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
p := syscall.Getpid()
log.Println("Process", p, "launching", c.String())
f(c)
}
func Launch(c *exec.Cmd, f func()) (e error) {
if e = c.Start(); e == nil {
f()
c.Wait()
}
if e == nil {
e = fmt.Errorf("%v: OK", c.String())
}
return
}

github://feyeleanor/2025golab
WAITING ON GOROUTINES TO COMPLETE HELPERS.GO
package main
import "sync"
func Parallelize[T any](s []T, f func(T)) {
var w sync.WaitGroup
for _, n := range s {
w.Add(1)
go func(n T) {
defer w.Done()
f(n)
}(n)
}
w.Wait()
}

github://feyeleanor/2025golab
WRAPPING A SHELL IN A SUBPROCESS 03.GO
package main
import (
"log"
"os"
"strings"
"syscall"
"time"
)
func main() {
Parallelize(os.Args[1:], func(s string) {
ShellCommand(s, func(p *os.Process) {
WaitSeconds(10, func() {
p.Kill()
})
})
})
}
func ShellCommand(s string, f func(*os.Process)) {
c := []string{os.Getenv("SHELL"), "-c", s}
log.Println("Process", syscall.Getpid(), "preparing", strings.Join(c, " "))
if p, e := os.StartProcess(c[0], c, &os.ProcAttr{Files: Stdio()}); e == nil {
f(p)
p.Wait()
}
}

github://feyeleanor/2025golab
HELPERS.GO
package main
import (
"os"
"time"
)
func Stdio() []*os.File {
return []*os.File{os.Stdin, os.Stdout, os.Stderr}
}
func WaitSeconds(n time.Duration, f func()) {
time.AfterFunc(n*time.Second, f)
}

github://feyeleanor/2025golab
SENDING SIGNALS TO A SUBPROCESS 04_PARENT.GO
package main
import (
"os"
"syscall"
)
func main() {
RunProgram("./04_child", func(p *os.Process) {
WaitSeconds(30, func() { p.Kill() })
EverySecond(20, func() { p.Signal(syscall.SIGINT) })
WaitSeconds(20, func() { p.Signal(syscall.SIGTERM) })
})
}
func RunProgram(s string, f func(*os.Process)) {
c := []string{s}
a := os.ProcAttr{Files: Stdio()}
if p, e := os.StartProcess(c[0], c, &a); e == nil {
LogPidf("process launched", p.Pid)
f(p)
p.Wait()
LogPidf("process finished", p.Pid)
}
}

github://feyeleanor/2025golab
PROCESS-AWARE LOGGING HELPERS.GO
package main
import (
"fmt"
"log"
"syscall"
)
func LogPidFatal(v ...any) {
log.Fatal(v...)
}
func LogPidln(v ...any) {
log.Println(
append(
[]any{fmt.Sprintf("%v:", syscall.Getpid())},
v...)...)
}
func LogPidf(s string, v ...any) {
log.Printf("%v: %v", syscall.Getpid(), fmt.Sprintf(s, v...))
}

github://feyeleanor/2025golab
PERIODIC REPEATS WITH TICKERS HELPERS.GO
package main
import (
"sync"
"time"
)
func EverySecond(n int, f func()) (w *sync.WaitGroup) {
ticker := time.NewTicker(1 * time.Second)
w = new(sync.WaitGroup)
w.Add(1)
go func() {
defer w.Done()
for {
select {
case <-ticker.C:
if n--; n > 0 {
f()
} else {
return
}
}
}
}()
return
}

github://feyeleanor/2025golab
SENDING SIGNALS TO A SUBPROCESS 04_CHILD.GO
package main
import (
"os"
"os/signal"
"syscall"
)
func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
for s := range c {
LogPidln("received", s)
if s == syscall.SIGTERM {
LogPidln("exiting")
close(c)
}
}
}

github://feyeleanor/2025golab
EXCHANGING SIGNALS WITH A SUBPROCESS 05_PARENT.GO
package main
import (
"os"
"syscall"
)
func main() {
RunProgram("./05_child", func(p *os.Process) {
HandleSignal(syscall.SIGUSR1, func() {
LogPidln("received SIGUSR1 from", pid)
})
WaitSeconds(30, func() { p.Kill() })
WaitSeconds(20, func() { p.Signal(syscall.SIGTERM) })
EverySecond(20, func() { p.Signal(syscall.SIGINT) })
})
}

github://feyeleanor/2025golab
HANDLING SIGNALS CONCURRENTLY HELPERS.GO
package main
import (
"os"
"os/signal"
)
func HandleSignal(s os.Signal, f func()) {
c := make(chan os.Signal)
signal.Notify(c, s)
go func(c chan os.Signal) {
for _ = range c {
f()
}
}(c)
}

github://feyeleanor/2025golab
EXCHANGING SIGNALS WITH A PARENT PROCESS 05_CHILD.GO
package main
import (
"os"
"os/signal"
"syscall"
)
func main() {
if pp, e := os.FindProcess(syscall.Getppid()); e == nil {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
for s := range c {
LogPidln("received", s)
switch s {
case syscall.SIGTERM:
LogPidln("exiting")
close(c)
case syscall.SIGINT:
pp.Signal(syscall.SIGUSR1)
}
}
}
}

github://feyeleanor/2025golab
EXCHANGING MESSAGES WITH A SUBPROCESS 06_PARENT.GO
package main
import (
"os"
"strings"
)
func main() {
t := TaskList("./06_child", 3)
c := []int{}
Parallelize(t, func(s string) {
RunProgram(s, func(p *os.Process, i, o *os.File) {
c = append(c, p.Pid)
WaitSeconds(10, func() {
p.Kill()
i.Close()
})
MessageLoop(i, func(s string) {
LogPidf("message [%v] from %v", s, p.Pid)
LogPidln("children:", c)
x := Peers(p, c...)
LogPidln("peers:", x)
SendMessage(o, strings.Join(x, " "))
})
})
})
}

github://feyeleanor/2025golab
GETTING SUBPROCESSES TO SIGNAL EACH OTHER 06_PARENT.GO
package main
import "os"
func RunProgram(s string, f func(*os.Process, *os.File, *os.File)) {
c := []string{s}
ChildStdio(func(io ...*os.File) {
a := os.ProcAttr{Files: []*os.File{io[2], io[1], os.Stderr}}
if p, e := os.StartProcess(c[0], c, &a); e == nil {
LogPidln("process launched", p.Pid)
f(p, io[0], io[3])
TryWait(p)
}
})
}

github://feyeleanor/2025golab
CONVENIENCES HELPERS.GO
package main
import (
"os"
"strconv"
)
func Peers(p *os.Process, c ...int) (r []string) {
for _, v := range c {
if v != p.Pid {
r = append(r, strconv.Itoa(v))
}
}
return
}
func TryWait(p *os.Process) {
if s, e := p.Wait(); e != nil {
LogPidln("unable to Wait on process", p.Pid)
} else {
LogPidln("process finished", s.Pid)
LogPidln(s.Pid, "exit code", s.ExitCode())
}
}

github://feyeleanor/2025golab
SENDING MESSAGES HELPERS.GO
package main
import "io"
func SendMessage(w io.Writer, s ...any) {
for _, v := range s {
switch v := v.(type) {
case []byte:
w.Write(v)
w.Write([]byte(string('')))
case string:
SendMessage(w, []byte(v))
case rune:
SendMessage(w, string(v))
case fmt.Stringer:
SendMessage(w, v.String())
default:
LogPidf("unable to send message [%T] %v", v, v)
}
}
}

github://feyeleanor/2025golab
RECEIVING AND ACTING ON MESSAGES HELPERS.GO
package main
import (
"bufio"
"io"
)
func MessageLoop(r io.Reader, f func(string)) {
for {
switch s, e := ReceiveMessage(r); e {
case nil:
for _, m := range Tokens(s) {
f(m)
}
case io.EOF:
for _, m := range Tokens(s) {
f(m)
}
return
default:
LogPidln(e)
return
}
}
}
func ReceiveMessage(r io.Reader) (b []byte, e error) {
switch b, e = bufio.NewReader(r).ReadBytes(''); {
case e == nil, e == io.EOF
b = DropTail(b, 1)
default:
LogPidln(e)
}
return
}

github://feyeleanor/2025golab
PIPEMANIA HELPERS.GO
package main
import "os"
type Pipe struct {
r, w *os.File
}
func Pipeio() Pipe {
in, out, e := os.Pipe()
if e != nil {
LogPidFatal(e)
}
return Pipe{in, out}
}
func ChildStdio(f func(...*os.File)) {
i := Pipeio()
o := Pipeio()
defer i.w.Close()
defer i.r.Close()
defer o.w.Close()
defer o.r.Close()
f(i.r, i.w, o.r, o.w)
}

github://feyeleanor/2025golab
CONVENIENCES HELPERS.GO
package main
import "strings"
func TaskList(s string, i int) (r []string) {
r = make([]string, 0, i)
for range i {
r = append(r, s)
}
return
}
func DropTail(b []byte, n int) []byte {
l := len(b) - n
if l < 0 {
l = 0
}
return b[:l]
}
func Tokens(b []byte) []string {
return strings.Split(string(b), string(' '))
}

github://feyeleanor/2025golab
ENABLING SUBPROCESSES TO SIGNAL EACH OTHER 06_CHILD.GO
package main
import (
"io"
"os"
"syscall"
)
func main() {
defer func() {
LogPidln("exiting")
}()
ForParent(func(p *os.Process) {
i := 1
HandleSignal(syscall.SIGUSR2, func() {
LogPidf("received SIGUSR2 x %v", i)
i++
})
EverySecond(5, func() {
SendMessage(os.Stdout, "HELLO")
ReceivePids(func(i int) {
ForProcess(i, func(p *os.Process) {
if i != syscall.Getpid() {
LogPidln("sending signal to", p.Pid)
p.Signal(syscall.SIGUSR2)
}
})
})
}).Wait()
})
}
func ReceivePids(f func(int)) {
switch b, e := ReceiveMessage(os.Stdin); e {
case nil, io.EOF:
ForEachInt(b, func(i int) {
f(i)
})
default:
LogPidln(e)
}
}

github://feyeleanor/2025golab
FINDING ANOTHER PROCESS HELPERS.GO
package main
import (
"os"
"syscall"
)
func ForParent(f func(*os.Process)) {
ForProcess(syscall.Getppid(), f)
}
func ForProcess(p int, f func(*os.Process)) {
LogPidln("ForProcess", p)
if p, e := os.FindProcess(p); e == nil {
if e := p.Signal(syscall.Signal(0)); e == nil {
f(p)
} else {
LogPidf("no such process as %v", p.Pid)
}
} else {
LogPidFatal(e)
}
}

github://feyeleanor/2025golab
PIPEMANIA HELPERS.GO
package main
import "os"
type Pipe struct {
r, w *os.File
}
func Pipeio() Pipe {
in, out, e := os.Pipe()
if e != nil {
LogPidFatal(e)
}
return Pipe{in, out}
}
func ChildStdio(f func(...*os.File)) {
i := Pipeio()
o := Pipeio()
defer i.w.Close()
defer i.r.Close()
defer o.w.Close()
defer o.r.Close()
f(i.r, i.w, o.r, o.w)
}

github://feyeleanor/2025golab
PIPEMANIA HELPERS.GO
package main
import "strconv"
func ForEachInt(s []byte, f func(int)) {
for _, m := range Tokens(s) {
if i, e := strconv.Atoi(m); e == nil {
f(i)
} else {
LogPidln(e, m)
}
}
}

github://feyeleanor/2025golab
READING MESSAGES FROM NAMED PIPES 07_SERVER.GO
package main
import (
"log"
"os"
"syscall"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("needs name of fifo to create")
}
m := map[int]bool{}
EverySecond(20, func() {
ForEachEntry(m, func(p *os.Process) {
log.Println("sending SIGUSR2 to", p.Pid)
p.Signal(syscall.SIGUSR2)
})
})
for {
FifoListen(os.Args[1], func(f *os.File) {
MessageLoop(f, func(s string) {
log.Printf("received pid [%v]", s)
ToggleEntry(m, s)
})
})
}
}

github://feyeleanor/2025golab
COMMUNICATING VIA NAMED PIPES 07_SERVER.GO
package main
import (
"log"
"os"
"golang.org/x/sys/unix"
)
func FifoListen(s string, f func(*os.File)) {
if _, e := os.Stat(s); e == nil {
if e = os.Remove(s); e != nil {
log.Fatal("cannot delete existing fifo")
}
}
if e := unix.Mkfifo(s, 0666); e != nil {
log.Fatal(e)
}
defer func() {
os.Remove(s)
}()
ForFifo(s, os.O_RDONLY, f)
}

github://feyeleanor/2025golab
HELPERS.GO
package main
import "os"
func ForFifo(s string, flag int, f func(*os.File)) {
if p, e := os.OpenFile(s, flag, os.ModeNamedPipe); e == nil {
defer p.Close()
f(p)
} else {
log.Fatal(e)
}
}

github://feyeleanor/2025golab
HELPERS.GO
package main
import (
"log"
"os"
"strconv"
)
func ForEachEntry(m map[int]bool, f func(p *os.Process)) {
for p, _ := range m {
ForProcess(p, f)
}
}
func ToggleEntry(c map[int]bool, s string) {
if i, e := strconv.Atoi(s); e == nil {
ForProcess(i, func(p *os.Process) {
if v, _ := c[p.Pid]; !v {
log.Println(p.Pid, "toggled on")
c[p.Pid] = true
} else {
log.Println(p.Pid, "toggled off")
delete(c, p.Pid)
}
})
} else {
log.Println(e)
}
}

github://feyeleanor/2025golab
WRITING MESSAGES TO NAMED PIPES 07_CLIENT.GO
package main
import (
"log"
"os"
"strconv"
"syscall"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("needs name of fifo to open")
}
i := 1
HandleSignal(syscall.SIGUSR2, func() {
log.Println("received SIGUSR2 x", i)
i++
})
FifoDial(os.Args[1], func(f *os.File) {
LogPidln("connected to Fifi", f.Fd())
EverySecond(10, func() {
SendPid(f)
}).Wait()
})
}
func FifoDial(s string, f func(*os.File)) {
LogPidln("connecting to Fifo", s)
if _, e := os.Stat(s); e != nil {
log.Fatal("cannot access fifo")
}
ForFifo(s, os.O_WRONLY, f)
}
func SendPid(f *os.File) {
SendMessage(f, strconv.Itoa(syscall.Getpid()))
}

github://feyeleanor/2025golab
CALLING C CODE WITH CGO 08.GO
package main
/*
#include <stdlib.h>
#include <stdio.h>
void hello() {
printf("hello from C");
}
void goodbye(char* s) {
printf("Goodbye %s", s);
}
*/
import "C"
import "unsafe"
func main() {
C.hello()
s := C.CString("cruel world!")
C.goodbye(s)
C.free(unsafe.Pointer(s))
}

github://feyeleanor/2025golab
WRAPPING SEMAPHORES WITH CGO 09_CREATE.GO
package main
import (
"log"
"os"
"time"
"unsafe"
)
/*
#include <sys/errno.h>
#include <stdlib.h>
#include <semaphore.h>
sem_t *go_sem_open(const char *name) {
return sem_open(name, O_CREAT, 0644,
1);
}
*/
import "C"
func main() {
if len(os.Args) < 2 {
log.Fatal("no semaphore name")
} else {
log.Print(os.Args[1])
}
name := C.CString(os.Args[1])
defer C.free(unsafe.Pointer(name))
s := C.go_sem_open(name)
log.Print("open")
if C.sem_wait(s) == 0 {
log.Print("locked")
time.Sleep(10 * time.Second)
log.Print("unlocking")
C.sem_post(s)
time.Sleep(10 * time.Second)
} else {
log.Print("can't lock")
}
C.sem_close(s)
C.sem_unlink(name)
}

github://feyeleanor/2025golab
WRAPPING SEMAPHORES WITH CGO 09_USE.GO
package main
import (
"log"
"os"
"time"
"unsafe"
)
/*
#include <stdlib.h>
#include <semaphore.h>
sem_t *go_sem_open(const char *name) {
return sem_open(name, 0);
}
*/
import "C"
func main() {
if len(os.Args) < 2 {
log.Fatal("name of semaphore required")
} else {
log.Print(os.Args[1])
}
name := C.CString(os.Args[1])
defer C.free(unsafe.Pointer(name))
s, e := C.go_sem_open(name)
if e != nil {
log.Println(e)
e = nil
}
if C.sem_wait(s) == 0 {
log.Print("acquired lock")
} else {
log.Print("cannot acquire lock")
}
C.sem_post(s)
log.Print("lock released")
C.sem_close(s)
C.sem_unlink(name)
}

github://feyeleanor/2025golab
WRAPPING SEMAPHORES WITH CGO 10_CREATE/*
package main
import (
"log"
"os"
"time"
"unsafe"
)
/*
#cgo CFLAGS: -g -Wall
#include "10_semaphore.h"
*/
import "C"
func main() {
if len(os.Args) < 2 {
log.Fatal("no semaphore name")
} else {
log.Print(os.Args[1])
}
name := C.CString(os.Args[1])
defer C.free(unsafe.Pointer(name))
s := C.go_sem_open(name)
log.Print("semaphore opening:")
if C.sem_wait(s) == 0 {
log.Print("locked semaphore")
time.Sleep(10 * time.Second)
log.Print("unlocking semaphore")
C.sem_post(s)
time.Sleep(10 * time.Second)
} else {
log.Print("can't lock semaphore")
}
C.sem_close(s)
C.sem_unlink(name)
}
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include <stdlib.h>
#include <semaphore.h>
sem_t *go_sem_open(const char*);
#endif
#include <semaphore.h>
sem_t *go_sem_open(const char *name) {
return sem_open(name, O_CREAT, 0644, 1);
}

github://feyeleanor/2025golab
WRAPPING SEMAPHORES WITH CGO 10_USE/*
package main
import (
"log"
"os"
"unsafe"
)
/*
#cgo CFLAGS: -g -Wall
#include "10_semaphore.h"
*/
import "C"
func main() {
if len(os.Args) < 2 {
log.Fatal("name of semaphore required")
} else {
log.Print(os.Args[1])
}
name := C.CString(os.Args[1])
defer C.free(unsafe.Pointer(name))
s, e := C.go_sem_open(name)
if e != nil {
log.Println(e)
e = nil
}
if C.sem_wait(s) == 0 {
log.Print("acquired lock")
} else {
log.Print("cannot acquire lock")
}
C.sem_post(s)
log.Print("lock released")
C.sem_close(s)
C.sem_unlink(name)
}
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include <stdlib.h>
#include <semaphore.h>
sem_t *go_sem_open(const char*);
#endif
#include <semaphore.h>
sem_t *go_sem_open(const char *name) {
return sem_open(name, O_CREAT, 0644, 1);
}

github://feyeleanor/2025golab
ENABLING SUBPROCESSES TO SIGNAL EACH OTHER 10_USE/*

github://feyeleanor/2025golab
WRAPPING SEMAPHORES WITH SYSCALL 11_CREATE.GO
package main
import (
"log"
"os"
"time"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("name of semaphore required")
}
SemUnlink(os.Args[1])
if s, e := SemOpen(os.Args[1]); e == nil {
log.Print("open")
if e := SemWait(s); e == nil {
log.Println("locked")
time.Sleep(10 * time.Second)
log.Println("unlocking")
SemPost(s)
time.Sleep(10 * time.Second)
} else {
log.Print(e)
}
SemClose(s)
SemUnlink(os.Args[1])
} else {
log.Fatal(e)
}
}

github://feyeleanor/2025golab
WRANGLING C STRINGS AND ERROR CODES HELPERS.GO
package main
import (
"errors"
"strconv"
"syscall"
"unsafe"
)
func CString(s string) unsafe.Pointer {
p, e := syscall.BytePtrFromString(s)
if e != nil {
log.Fatalf("unable to convert %v to a C-style string", s)
}
return unsafe.Pointer(p)
}
func SyscallError(r uintptr, e syscall.Errno, s string) error {
if r != 0 {
return ErrorCode(s, e)
}
return nil
}
func ErrorCode(s string, e syscall.Errno) error {
return errors.New(s + ": " + strconv.FormatUint(uint64(e), 10))
}

github://feyeleanor/2025golab
WRAPPING SEMAPHORES WITH SYSCALL 11_CREATE.GO

github://feyeleanor/2025golab
ENABLING SUBPROCESSES TO SIGNAL EACH OTHER SYSCALLS.GO
//go:build darwin
package main
import "golang.org/x/sys/unix"
func Syscall1(op, v uintptr, s string) error {
r, _, e := unix.Syscall(op, v, 0, 0)
return SyscallError(r, e, s)
}
func SemOpen(s string) (uintptr, error) {
r, _, e := unix.Syscall6(
268,
uintptr(CString(s)),
unix.O_CREAT,
0644,
1, 0, 0)
if r == 0 {
return 0, ErrorCode("open failed", e)
}
return r, nil
}
func SemClose(s uintptr) error {
return Syscall1(269, s, "close failed")
}
func SemUnlink(s string) error {
n := uintptr(CString(s))
return Syscall1(270, n, "unlink failed")
}
func SemWait(s uintptr) error {
return Syscall1(271, s, "wait failed")
}
func SemTryWait(s uintptr) error {
return Syscall1(272, s, "trywait failed")
}
func SemPost(s uintptr) error {
return Syscall1(273, s, "post failed")
}

github://feyeleanor/2025golab
ENABLING SUBPROCESSES TO SIGNAL EACH OTHER SYSCALLS.GO
//go:build linux && cgo
package main
import (
"errors"
"fmt"
"unsafe"
)
/*
#include <fcntl.h>
#include <sys/errno.h>
#include <stdlib.h>
#include <semaphore.h>
sem_t *go_sem_open(const char *name) {
return sem_open(name, O_CREAT, 0644, 1);
}
*/
import "C"
func sem_t_to_uintptr(s *C.sem_t) uintptr {
return uintptr(unsafe.Pointer(s))
}
func unitptr_to_sem_t(p uintptr) *C.sem_t {
return (*C.sem_t)(unsafe.Pointer(p))
}
func SemOpen(s string) (r uintptr, e error) {
if r = sem_t_to_uintptr(C.go_sem_open(C.CString(s))); r == 0 {
e = errors.New("open failed")
}
return
}
func SemClose(s uintptr) (e error) {
if r := C.sem_close(unitptr_to_sem_t(s)); r != 0 {
e = errors.New(fmt.Sprint("close failed: %v", r))
}
return
}
func SemUnlink(s string) (e error) {
if r := C.sem_unlink(C.CString(s)); r != 0 {
e = errors.New(fmt.Sprint("unlink failed: %v", r))
}
return
}
func SemWait(s uintptr) (e error) {
if r := C.sem_wait(unitptr_to_sem_t(s)); r != 0 {
e = errors.New(fmt.Sprint("wait failed: %v", r))
}
return
}
func SemTryWait(s uintptr) (e error) {
if r := C.sem_trywait(unitptr_to_sem_t(s)); r != 0 {
e = errors.New(fmt.Sprint("trywait failed: %v", r))
}
return
}
func SemPost(s uintptr) (e error) {
if r := C.sem_post(unitptr_to_sem_t(s)); r != 0 {
e = errors.New(fmt.Sprint("post failed: %v", r))
}
return
}

github://feyeleanor/2025golab
WRAPPING SEMAPHORES WITH SYSCALL 11_USE.GO
package main
import (
"log"
"os"
"time"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("name of semaphore required")
}
if s, e := SemOpen(os.Args[1]); e == nil {
log.Print("open")
if e := SemWait(s); e == nil {
log.Print("locked")
time.Sleep(10 * time.Second)
log.Println("unlocking")
SemPost(s)
} else {
log.Println(e)
}
SemClose(s)
SemUnlink(os.Args[1])
} else {
log.Fatal(e)
}
}

github://feyeleanor/2025golab
COMMUNICATING OVER DOMAIN SOCKETS 12_SERVER.GO
package main
import (
"log"
"net"
"os"
"os/signal"
"syscall"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("needs name of domain socket to create")
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
os.Remove(os.Args[1])
os.Exit(1)
}()
if s, e := net.Listen("unix", os.Args[1]); e == nil {
OnConnection(s, func(c net.Conn) {
MessageLoop(c, func(s string) {
log.Println("Received:", s)
// there's more to reversing a string than just reversing the order of the
// runes but for our purposes we'll assume it's plain ASCII
// see https://github.com/shomali11/util/blob/master/xstrings/xstrings.go
SendMessage(c, string(Reverse([]rune(s))))
})
})
} else {
log.Fatal(e)
}
}

github://feyeleanor/2025golab
LISTENING FOR CONNECTIONS ON SOCKETS HELPERS.GO
package main
import (
"log"
"net"
)
func OnConnection(s net.Listener, f func(net.Conn)) {
for {
if c, e := s.Accept(); e == nil {
f(c)
} else {
log.Fatal(e)
}
}
}

github://feyeleanor/2025golab
GENERIC FUNCTIONAL REVERSAL OF A SLICE HELPERS.GO
package main
func Reverse[T ~[]E, E comparable](s T) (r T) {
l := len(s)
r = make(T, l)
if m := l / 2; m == 0 {
copy(r, s)
} else {
r[m] = s[m]
for i, j := 0, l-1; i < m; i, j = i+1, j-1 {
r[i], r[j] = s[j], s[i]
}
}
return
}

github://feyeleanor/2025golab
COMMUNICATING OVER DOMAIN SOCKETS 12_CLIENT.GO
package main
import (
"log"
"net"
"os"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("needs name of domain socket to create")
}
DialServer("unix", os.Args[1], func(c net.Conn) {
for _, n := range os.Args[2:] {
SendMessage(c, n)
if m, e := ReceiveMessage(c); e == nil {
log.Printf("sent [%v], received [%v]", n, string(m))
} else {
log.Println(e)
}
}
})
}

github://feyeleanor/2025golab
LISTENING FOR CONNECTIONS ON SOCKETS HELPERS.GO
package main
import (
"context"
"log"
"net"
"time"
)
func DialServer(p, a string, f func(net.Conn)) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
var d net.Dialer
if c, e := d.DialContext(ctx, p, a); e == nil {
defer c.Close()
f(c)
} else {
log.Println(e)
}
}

github://feyeleanor/2025golab
COMMUNICATING WITH JSON OVER DOMAIN SOCKETS 13_SERVER.GO
package main
import (
"encoding/json"
"log"
"net"
"os"
"os/signal"
"syscall"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("needs name of domain socket to create")
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
os.Remove(os.Args[1])
os.Exit(1)
}()
if s, e := net.Listen("unix", os.Args[1]); e == nil {
OnConnection(s, func(c net.Conn) {
MessageLoop(c, func(s string) {
log.Println("Received:", s)
m := TransformJSON(s, func(v string) string {
return string(Reverse([]rune(v)))
})
SendMessage(c, m)
})
})
} else {
log.Fatal(e)
}
}
func TransformJSON[T any](s string, f func(T) T) string {
m := []any{}
if e := json.Unmarshal([]byte(s), &m); e == nil {
log.Println(m)
for i, v := range m {
if v, ok := v.(T); ok {
m[i] = f(v)
}
}
} else {
log.Println(e)
}
r, _ := json.Marshal(m)
return string(r)
}

github://feyeleanor/2025golab
COMMUNICATING OVER DOMAIN SOCKETS 13_CLIENT.GO
package main
import (
"encoding/json"
"log"
"net"
"os"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("needs name of domain socket to create")
}
DialServer("unix", os.Args[1], func(c net.Conn) {
if len(os.Args) > 2 {
s, _ := json.Marshal(os.Args[2:])
SendMessage(c, s)
if m, e := ReceiveMessage(c); e == nil {
ProcessJSON(m, func(i int, v string) {
log.Printf("sent [%v], received [%v]", os.Args[i+2], v)
})
} else {
log.Println(e)
}
}
})
}
func ProcessJSON[T any](b []byte, f func(int, T)) {
m := []any{}
if e := json.Unmarshal(b, &m); e == nil {
log.Println(m)
for i, v := range m {
if v, ok := v.(T); ok {
f(i, v)
}
}
} else {
log.Println(e)
}
}

IN DEVELOPMENT
▸shared memory
▸memory mapped files
▸daemon services
▸message queues
▸terminal i/o
▸writing a shell

PART 2
THINGS NOT TO DO ON REMOTELY

LEANPUB://GONOTEBOOK [GITHUB | SLIDESHARE]://FEYELEANOR
https://www.kegel.com/c10k.html
https://beej.us/guide/
https://wiki.netbsd.org/tutorials/kqueue_tutorial/