A GOPHER'S GUIDE TO
*NIX
ELEANOR MCHUGH
@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
fi
le system contains inodes
▸ inode identi
fi
es
fi
les and block devices
▸ block device is an i/o resource
▸
fi
le 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 de
fi
ned 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
fl
exible
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('n')))
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('n'); {
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 Cn");
}
void goodbye(char* s) {
printf("Goodbye %sn", s);
}
*/
import "C"
import "unsafe"
func main() {
C.hello()
s := C.CString("cruel world!n")
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
fi
les
▸ 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/

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

  • 1.
    A GOPHER'S GUIDETO *NIX ELEANOR MCHUGH @feyeleanor Workshop Remixes!
  • 2.
  • 3.
  • 6.
    *NIX ARCHITECTURE INA 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 fi le system contains inodes ▸ inode identi fi es fi les and block devices ▸ block device is an i/o resource ▸ fi le contains bytes stored on a device ▸ shell is an interactive command line
  • 7.
    A PRACTICAL DIYPHILOSOPHY ▸ a bit anarchic, a bit punk ▸ create small tools to solve clearly de fi ned 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 fl exible
  • 8.
    PART 1 THINGS NOTTO DO LOCALLY
  • 9.
    github://feyeleanor/2025golab EXECUTING A COMMANDIN 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) } }
  • 10.
    github://feyeleanor/2025golab WRAPPING A SHELLIN 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 }
  • 11.
    github://feyeleanor/2025golab WAITING ON GOROUTINESTO 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() }
  • 12.
    github://feyeleanor/2025golab WRAPPING A SHELLIN 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() } }
  • 13.
    github://feyeleanor/2025golab HELPERS.GO package main import ( "os" "time" ) funcStdio() []*os.File { return []*os.File{os.Stdin, os.Stdout, os.Stderr} } func WaitSeconds(n time.Duration, f func()) { time.AfterFunc(n*time.Second, f) }
  • 14.
    github://feyeleanor/2025golab SENDING SIGNALS TOA 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) } }
  • 15.
    github://feyeleanor/2025golab PROCESS-AWARE LOGGING HELPERS.GO packagemain 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...)) }
  • 16.
    github://feyeleanor/2025golab PERIODIC REPEATS WITHTICKERS 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 }
  • 17.
    github://feyeleanor/2025golab SENDING SIGNALS TOA 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) } } }
  • 18.
    github://feyeleanor/2025golab EXCHANGING SIGNALS WITHA 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) }) }) }
  • 19.
    github://feyeleanor/2025golab HANDLING SIGNALS CONCURRENTLYHELPERS.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) }
  • 20.
    github://feyeleanor/2025golab EXCHANGING SIGNALS WITHA 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) } } } }
  • 21.
    github://feyeleanor/2025golab EXCHANGING MESSAGES WITHA 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, " ")) }) }) }) }
  • 22.
    github://feyeleanor/2025golab GETTING SUBPROCESSES TOSIGNAL 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) } }) }
  • 23.
    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()) } }
  • 24.
    github://feyeleanor/2025golab SENDING MESSAGES HELPERS.GO packagemain 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('n'))) 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) } } }
  • 25.
    github://feyeleanor/2025golab RECEIVING AND ACTINGON 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('n'); { case e == nil, e == io.EOF b = DropTail(b, 1) default: LogPidln(e) } return }
  • 26.
    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) }
  • 27.
    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(' ')) }
  • 28.
    github://feyeleanor/2025golab ENABLING SUBPROCESSES TOSIGNAL 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) } }
  • 29.
    github://feyeleanor/2025golab FINDING ANOTHER PROCESSHELPERS.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) } }
  • 30.
    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) }
  • 31.
    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) } } }
  • 32.
    github://feyeleanor/2025golab READING MESSAGES FROMNAMED 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) }) }) } }
  • 33.
    github://feyeleanor/2025golab COMMUNICATING VIA NAMEDPIPES 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) }
  • 34.
    github://feyeleanor/2025golab HELPERS.GO package main import "os" funcForFifo(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) } }
  • 35.
    github://feyeleanor/2025golab HELPERS.GO package main import ( "log" "os" "strconv" ) funcForEachEntry(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) } }
  • 36.
    github://feyeleanor/2025golab WRITING MESSAGES TONAMED 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())) }
  • 37.
    github://feyeleanor/2025golab CALLING C CODEWITH CGO 08.GO package main /* #include <stdlib.h> #include <stdio.h> void hello() { printf("hello from Cn"); } void goodbye(char* s) { printf("Goodbye %sn", s); } */ import "C" import "unsafe" func main() { C.hello() s := C.CString("cruel world!n") C.goodbye(s) C.free(unsafe.Pointer(s)) }
  • 38.
    github://feyeleanor/2025golab WRAPPING SEMAPHORES WITHCGO 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) }
  • 39.
    github://feyeleanor/2025golab WRAPPING SEMAPHORES WITHCGO 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) }
  • 40.
    github://feyeleanor/2025golab WRAPPING SEMAPHORES WITHCGO 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); }
  • 41.
    github://feyeleanor/2025golab WRAPPING SEMAPHORES WITHCGO 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); }
  • 42.
  • 43.
    github://feyeleanor/2025golab WRAPPING SEMAPHORES WITHSYSCALL 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) } }
  • 44.
    github://feyeleanor/2025golab WRANGLING C STRINGSAND 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)) }
  • 45.
  • 46.
    github://feyeleanor/2025golab ENABLING SUBPROCESSES TOSIGNAL 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") }
  • 47.
    github://feyeleanor/2025golab ENABLING SUBPROCESSES TOSIGNAL 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 }
  • 48.
    github://feyeleanor/2025golab WRAPPING SEMAPHORES WITHSYSCALL 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) } }
  • 49.
    github://feyeleanor/2025golab COMMUNICATING OVER DOMAINSOCKETS 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) } }
  • 50.
    github://feyeleanor/2025golab LISTENING FOR CONNECTIONSON 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) } } }
  • 51.
    github://feyeleanor/2025golab GENERIC FUNCTIONAL REVERSALOF 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 }
  • 52.
    github://feyeleanor/2025golab COMMUNICATING OVER DOMAINSOCKETS 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) } } }) }
  • 53.
    github://feyeleanor/2025golab LISTENING FOR CONNECTIONSON 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) } }
  • 54.
    github://feyeleanor/2025golab COMMUNICATING WITH JSONOVER 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) }
  • 55.
    github://feyeleanor/2025golab COMMUNICATING OVER DOMAINSOCKETS 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) } }
  • 56.
    IN DEVELOPMENT ▸ sharedmemory ▸ memory mapped fi les ▸ daemon services ▸ message queues ▸ terminal i/o ▸ writing a shell
  • 57.
    PART 2 THINGS NOTTO DO ON REMOTELY
  • 58.
    LEANPUB://GONOTEBOOK [GITHUB |SLIDESHARE]://FEYELEANOR https://www.kegel.com/c10k.html https://beej.us/guide/ https://wiki.netbsd.org/tutorials/kqueue_tutorial/