Henka is a minimal Go abstraction layer over the go9p library that simplifies creating 9P filesystem servers by providing flat path-based storage, automatic directory creation, and support for static values or io.Reader/io.Writer interfaces as file contents.
  • Go 95.9%
  • Makefile 4.1%
Find a file
2026-04-02 09:01:36 -07:00
docs chore: move documentation files to docs directory 2026-01-31 08:00:38 -08:00
.gitignore chore: update gitignore to include common binary, dependency, and IDE files 2026-04-01 17:07:37 -07:00
.golangci.yml chore: add golangci-lint and pre-commit configurations 2026-04-01 17:09:53 -07:00
.pre-commit-config.yaml chore: add golangci-lint and pre-commit configurations 2026-04-01 17:09:53 -07:00
go.mod feat: add client API and external server testing support 2026-04-02 09:01:36 -07:00
go.sum feat: add go9p dependency for 9P protocol support 2026-04-01 17:08:08 -07:00
henka.go feat: add client API and external server testing support 2026-04-02 09:01:36 -07:00
henka_test.go feat: add client API and external server testing support 2026-04-02 09:01:36 -07:00
Makefile feat: add Makefile for project automation and development tools 2026-04-01 17:09:44 -07:00
README.md feat: add client API and external server testing support 2026-04-02 09:01:36 -07:00

Table of Contents

  1. henka - Simplified 9P Server Abstraction
    1. Overview
    2. Integration with go9p
    3. Core Types
      1. Node
      2. Abstraction (implements fs.FS)
    4. Path Resolution
    5. Simple API
      1. Setup
      2. Automatic Directory Creation
      3. Supported Value Types
    6. I/O Behavior
      1. Reading
      2. Writing
    7. Permissions
    8. Complete Example
    9. Error Handling
    10. Advantages for go9p Users
    11. Client API
    12. Development
    13. Notes

henka - Simplified 9P Server Abstraction

Overview

Minimal abstraction layer over github.com/knusbaum/go9p for easy 9P server setup. Flat map storage with path-based semantics. Supports static values and io.Reader/io.Writer interfaces.

Integration with go9p

// henka provides a complete 9P server implementation:
h := henka.New()
// ... add nodes ...
server := h.NewServer(true) // dotu = true/false
// Server implements go9p.Srv and is ready to use with go9p listeners

// Alternative: Get server directly
server := h.Server() // Equivalent to h.NewServer(true)

Core Types

Node

type Node struct {
    Name  string        // Full path: "dir/file"
    IsDir bool          // Directory marker
    Value interface{}   // Content: static or io.Reader/io.Writer
    Mode  os.FileMode   // Permissions (default: 0644 files, 0755 dirs)
}

Abstraction (provides filesystem root for go9p)

type Abstraction struct {
    nodes map[string]*Node
    mu    sync.RWMutex
}

Path Resolution

  • Paths normalized (no trailing "/")
  • Directories auto-created when needed
  • Example: Adding "/a/b/c" creates "/a" and "/a/b" as directories if missing

Simple API

Setup

// Create and populate
fs := henka.New()

// Add directory (optional, will auto-create)
fs.AddNode("/tmp", nil, true)

// Add file with static value
fs.AddNode("/tmp/msg", "Hello 9P", false)
fs.AddNode("/tmp/count", int64(42), false)

// Add io.Writer for logging
var buf bytes.Buffer
fs.AddNode("/tmp/log", &buf, false)

// Ready for go9p
server := fs.NewServer(true)

Automatic Directory Creation

Directories are implicitly created when adding files:

// This creates /config and /config/db as directories automatically
fs.AddNode("/config/db/host", "localhost", false)

Supported Value Types

  • Read/Write: []byte, *bytes.Buffer, string, int64, float64
  • Write-only: io.Writer (append only, offset must be 0), io.WriterAt (arbitrary offsets)
  • Read-only: io.Reader (reads from beginning), io.ReaderAt (arbitrary offsets)
  • Directories: Value = nil, IsDir = true

Note: Types implementing multiple interfaces (io.ReadWriter, io.ReadWriteSeeker) will work according to their implemented interfaces.

I/O Behavior

Reading

// All read operations through henka.Read()
data, err := fs.Read("/tmp/msg", 0, 100)
  1. io.ReaderAt: ReadAt(offset, count)
  2. io.Reader: Reads all data from beginning, then slices (no seeking)
  3. Static types: Return appropriate slice
    • []byte, *bytes.Buffer: slice[offset:offset+count]
    • string: UTF-8 bytes
    • int64/float64: binary representation (8 bytes)
  4. Returns error if unsupported

Writing

// All write operations through henka.Write()
err := fs.Write("/tmp/count", 0, []byte{0,0,0,0,0,0,0,43})
  1. io.WriterAt: WriteAt(data, offset)
  2. io.Writer: Write(data) (offset must be 0, append only)
  3. []byte: modify slice at offset, extends if needed
  4. *bytes.Buffer: writes at offset, extends with zeros if needed
  5. Other static types (string, int64, float64): replace entire value (offset must be 0)
  6. Returns error if unsupported

Permissions

  • Default: files 0644, directories 0755

  • Customizable per node:

    fs.AddNodeWithMode("/secret", "data", false, 0600)

Complete Example

package main

import (
    "bytes"
    "net"
    
    "github.com/knusbaum/go9p"
    "git.lan.thwap.org/thwap/henka"
)

func main() {
    // Create henka filesystem
    h := henka.New()

    // Add some files
    h.AddNode("/README", "Welcome to 9P", false)
    h.AddNode("/count", int64(0), false)

    // Add a directory with files
    h.AddNode("/logs/access", &bytes.Buffer{}, false)
    h.AddNode("/logs/error", &bytes.Buffer{}, false)

    // Create go9p server
    server := h.NewServer(true)

    // Start listening using go9p.Serve
    listener, _ := net.Listen("tcp", ":5640")
    go9p.Serve(listener, server)
}

Error Handling

  • Missing parent directories: auto-created (simplifies setup)
  • Invalid paths: returns error
  • Unsupported operations: returns clear error

Advantages for go9p Users

  1. No boilerplate: Skip manual fs.Node implementation
  2. Automatic structure: Paths automatically create directory hierarchy
  3. Flexible backends: Use static data, buffers, or custom readers/writers
  4. Simple integration: Just create a henka instance with henka.New() and call NewServer()

Client API

The library includes a client for connecting to remote 9P servers:

import "git.lan.thwap.org/thwap/henka"

// Connect to a 9P server
client, err := henka.NewClient("localhost:564")
if err != nil {
    log.Fatal(err)
}
defer client.Close()

// List directory contents
entries, err := client.Ls("/")
if err != nil {
    log.Fatal(err)
}
for _, e := range entries {
    fmt.Printf("%s %d %v\n", e.Name, e.Size, e.ModTime)
}

// Read file data
data, err := client.Read("/file.txt", 0, 100)
if err != nil {
    log.Fatal(err)
}

// Write to file
err = client.Write("/file.txt", 0, []byte("new content"))
if err != nil {
    log.Fatal(err)
}

Testing with External Servers

Client tests can run against an external 9P server by setting the 9P_SERVER environment variable:

export 9P_SERVER="localhost:564"
go test -v -run TestClient

This is useful for integration testing with compliant 9P servers.

Development

The project includes comprehensive testing and quality assurance tools:

  • Testing: Run go test ./... or make test for unit tests
  • Race Detection: Run go test -race ./... to detect concurrency issues
  • Code Quality: Pre-commit hooks enforce formatting and linting (see .pre-commit-config.yaml)
  • Build: make build compiles the package
  • Formatting: make fmt runs go fmt

See Makefile for all available commands.

Notes

  • Designed specifically for go9p compatibility
  • Minimal overhead over raw go9p
  • Focus on developer ergonomics for common 9P server use cases