Skip to content

API Reference

Brian Jipson edited this page Dec 27, 2025 · 2 revisions

API Reference

This page documents all 60+ host service operations available to plugins. All operations use structured logging with request/completion tracking, client identification, and microsecond-precision metrics.

API Design Patterns

The host service API follows two consistent patterns:

1. RootDir + Path Pattern (Unary Operations)

All unary operations and methods that establish file handles use a rootDir + path pattern:

// Pattern: operation(ctx, rootDir, path, ...other parameters)
data, err := hostServiceClient.ReadFile(ctx, rootDir, path)
err = hostServiceClient.WriteFile(ctx, rootDir, path, data, perm)
handle, size, err := hostServiceClient.FileOpen(ctx, rootDir, path, flags, perm)

Requirements:

  • rootDir: MUST be an absolute path (e.g., /home/user, /var/data)
  • path: CAN be absolute or relative, but MUST NOT escape the rootDir

Why this pattern?

  • Security: Confines operations within a specific root directory, preventing path traversal attacks
  • Clarity: Explicitly separates the security boundary (rootDir) from the target location (path)
  • Flexibility: Plugins can pass absolute or relative paths while the host enforces confinement

2. Handle Pattern (Operations on Established Resources)

Once a file handle is established, all subsequent operations use only the handle:

// First, establish the resource and get a handle
handle, size, err := hostServiceClient.FileOpen(ctx, rootDir, path, flags, perm)

// Then, operate using only the handle - no paths needed
reader, err := hostServiceClient.FileReader(ctx, handle, chunkSize)
offset, err := hostServiceClient.FileSeek(ctx, handle, offset, whence)
info, err := hostServiceClient.FileStat(ctx, handle)
err = hostServiceClient.FileClose(ctx, handle)

Why this pattern?

  • Performance: Path resolution and security checks happen once at open time
  • Stateful operations: Supports file position (seek), progressive reading/writing
  • Resource management: Explicit lifecycle with open/close
  • Familiar: Mirrors standard file I/O patterns

Filesystem Operations (Path-Based)

Directory Operations

Operation Signature stdlib Equivalent Description
ReadDir (ctx, rootDir, path) os.ReadDir Read directory contents
Mkdir (ctx, rootDir, name, perm) os.Mkdir Create single directory
MkdirAll (ctx, rootDir, path, perm) os.MkdirAll Create directory with parents
MkdirTemp (ctx, rootDir, pattern) os.MkdirTemp Create temporary directory (auto-cleanup)

Example: ReadDir

entries, err := hostServiceClient.ReadDir(ctx, "/home/user", "documents")
if err != nil {
    return err
}

for _, entry := range entries {
    fmt.Printf("%s (dir: %v)\n", entry.Name(), entry.IsDir())
}

Example: MkdirTemp

// Create temp directory - automatically cleaned up when plugin disconnects
tempDir, err := hostServiceClient.MkdirTemp(ctx, "", "myapp-*")
if err != nil {
    return err
}
fmt.Printf("Created temp dir: %s\n", tempDir)
// No cleanup needed - host handles it!

File Reading/Writing

Operation Signature stdlib Equivalent Description
ReadFile (ctx, rootDir, path) os.ReadFile Read entire file contents
WriteFile (ctx, rootDir, path, data, perm) os.WriteFile Write entire file contents

Example: ReadFile

data, err := hostServiceClient.ReadFile(ctx, "/home/user", "config.json")
if err != nil {
    return err
}

// Parse data
var config Config
json.Unmarshal(data, &config)

Example: WriteFile

data := []byte("Hello, World!")
err := hostServiceClient.WriteFile(ctx, "/home/user", "output.txt", data, 0644)
if err != nil {
    return err
}

File Information

Operation Signature stdlib Equivalent Description
Stat (ctx, rootDir, path) os.Stat Get file/directory info
Lstat (ctx, rootDir, path) os.Lstat Get file info without following symlinks

Example: Stat

fileInfo, err := hostServiceClient.Stat(ctx, "/home/user", "data.txt")
if err != nil {
    return err
}
fmt.Printf("Size: %d, Mode: %v, ModTime: %v\n",
    fileInfo.Size(), fileInfo.Mode(), fileInfo.ModTime())

File Management

Operation Signature stdlib Equivalent Description
Rename (ctx, rootDir, oldPath, newPath) os.Rename Rename or move file/directory
Remove (ctx, rootDir, path) os.Remove Delete file or empty directory
RemoveAll (ctx, rootDir, path) os.RemoveAll Recursively delete directory and contents

⚠️ Warning: These operations can permanently delete data. Always validate paths and consider confirmation mechanisms.

Example: Rename

err := hostServiceClient.Rename(ctx, "/home/user", "old-name.txt", "new-name.txt")
if err != nil {
    return err
}

Example: RemoveAll

// ⚠️ USE WITH EXTREME CAUTION - deletes directory and ALL contents
err := hostServiceClient.RemoveAll(ctx, "/home/user", "old-project")
if err != nil {
    return err
}

Symbolic and Hard Links

Operation Signature stdlib Equivalent Description
Symlink (ctx, rootDir, oldPath, newPath) os.Symlink Create symbolic link
Link (ctx, rootDir, oldPath, newPath) os.Link Create hard link
Readlink (ctx, rootDir, path) os.Readlink Read symbolic link target

Example: Creating and Reading Symlinks

// Create symbolic link
err := hostServiceClient.Symlink(ctx, "/home/user", "original.txt", "link.txt")
if err != nil {
    return err
}

// Read where the link points
target, err := hostServiceClient.Readlink(ctx, "/home/user", "link.txt")
if err != nil {
    return err
}
fmt.Printf("Link points to: %s\n", target)

// Get info about the link itself (not the target)
linkInfo, err := hostServiceClient.Lstat(ctx, "/home/user", "link.txt")

Permissions and Ownership

Operation Signature stdlib Equivalent Description
Chmod (ctx, rootDir, path, mode) os.Chmod Change file permissions
Chown (ctx, rootDir, path, uid, gid) os.Chown Change file ownership
Lchown (ctx, rootDir, path, uid, gid) os.Lchown Change ownership without following symlinks
Chtimes (ctx, rootDir, path, atime, mtime) os.Chtimes Change access/modification times

Example: Chmod

// Make script executable
err := hostServiceClient.Chmod(ctx, "/home/user", "script.sh", 0755)
if err != nil {
    return err
}

Example: Chtimes

atime := time.Now()
mtime := time.Now().Add(-24 * time.Hour)
err := hostServiceClient.Chtimes(ctx, "/home/user", "file.txt", atime, mtime)

Filesystem Operations (Handle-Based)

Opening and Creating Files

Operation Signature stdlib Equivalent Description
FileOpen (ctx, rootDir, path, flag, perm) os.OpenFile Open file and return handle with size
FileCreate (ctx, rootDir, path) os.Create Create or truncate file
FileCreateTemp (ctx, rootDir, pattern) os.CreateTemp Create temporary file (auto-cleanup)
FileClose (ctx, handle) (*os.File).Close Close file handle

Example: FileOpen

// Open file for reading
handle, size, err := hostServiceClient.FileOpen(ctx, "/home/user", "data.bin", os.O_RDONLY, 0)
if err != nil {
    return err
}
defer hostServiceClient.FileClose(ctx, handle)

fmt.Printf("Opened file with handle: %s, size: %d\n", handle, size)

Example: FileCreateTemp

// Create temporary file - automatically cleaned up
handle, err := hostServiceClient.FileCreateTemp(ctx, "", "work-*.tmp")
if err != nil {
    return err
}
defer hostServiceClient.FileClose(ctx, handle)

// Use the file...

File Handle Operations

Operation Signature stdlib Equivalent Description
FileStat (ctx, handle) (*os.File).Stat Get file info for open handle
FileSeek (ctx, handle, offset, whence) (*os.File).Seek Seek to position in file
FileSync (ctx, handle) (*os.File).Sync Flush buffered data to disk
FileTruncate (ctx, handle, size) (*os.File).Truncate Resize file to specified size
FileChmod (ctx, handle, mode) (*os.File).Chmod Change permissions on open file
FileChown (ctx, handle, uid, gid) (*os.File).Chown Change ownership on open file

Example: FileSeek

// Seek to position 1024 from start
newOffset, err := hostServiceClient.FileSeek(ctx, handle, 1024, 0)
if err != nil {
    return err
}
fmt.Printf("New position: %d\n", newOffset)

Example: FileTruncate

// Resize file to 1024 bytes
err := hostServiceClient.FileTruncate(ctx, handle, 1024)
if err != nil {
    return err
}

// Ensure changes are written to disk
err = hostServiceClient.FileSync(ctx, handle)

Streaming Operations

Operation Signature stdlib Equivalent Description
FileReader (ctx, handle, chunkSize) io.Reader Stream file contents in chunks (8KB-3.81MB)
FileWriter (ctx, handle) io.WriteCloser Stream write to file
FileReadSection (ctx, handle, offset, size) (*os.File).ReadAt Read specific section of file at offset
FileWriteSection (ctx, handle, offset, maxLen, data) (*os.File).WriteAt Write data to specific section at offset

Example: FileReader (Streaming)

// Open file
handle, _, err := hostServiceClient.FileOpen(ctx, "/home/user", "large-file.bin", os.O_RDONLY, 0)
if err != nil {
    return err
}
defer hostServiceClient.FileClose(ctx, handle)

// Stream read with 64KB chunks
chunkSize := uint32(64 * 1024)
stream, err := hostServiceClient.FileReader(ctx, handle, chunkSize)
if err != nil {
    return err
}

// Read chunks
buffer := make([]byte, 4096)
for {
    n, err := stream.Read(buffer)
    if n > 0 {
        // Process buffer[:n]
        fmt.Printf("Read %d bytes\n", n)
    }
    if err == io.EOF {
        break
    }
    if err != nil {
        return err
    }
}

Chunk Size Guidelines:

  • Minimum: 8KB (8,192 bytes) - Enforced minimum
  • Maximum: ~3.81MB (4,000,000 bytes) - gRPC message limit
  • Recommended: 64KB-1MB - Best balance for most use cases

Example: FileWriter (Streaming)

// Open file for writing
handle, _, err := hostServiceClient.FileOpen(ctx, "/home/user", "output.bin", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
    return err
}
defer hostServiceClient.FileClose(ctx, handle)

// Get writer
writer, err := hostServiceClient.FileWriter(ctx, handle)
if err != nil {
    return err
}

// Write data in chunks
data := []byte("large amount of data...")
chunkSize := 64 * 1024

for i := 0; i < len(data); i += chunkSize {
    end := i + chunkSize
    if end > len(data) {
        end = len(data)
    }

    n, err := writer.Write(data[i:end])
    if err != nil {
        return err
    }
    fmt.Printf("Wrote %d bytes\n", n)
}

// Finish writing
err = writer.Close()

Example: FileReadSection (Random Access)

// Read 100 bytes starting at offset 4096
offset := int64(4096)
size := int32(100)

data, err := hostServiceClient.FileReadSection(ctx, handle, offset, size)
if err != nil {
    return err
}
fmt.Printf("Read %d bytes from offset %d: %s\n", len(data), offset, string(data))

Example: FileWriteSection (Random Access)

// Write data at specific offset
offset := int64(1024)
maxLength := int32(50)
data := []byte("Update at offset")

bytesWritten, err := hostServiceClient.FileWriteSection(ctx, handle, offset, maxLength, data)
if err != nil {
    return err
}
fmt.Printf("Wrote %d bytes at offset %d\n", bytesWritten, offset)

// Ensure changes are synced
err = hostServiceClient.FileSync(ctx, handle)

Use Cases for Section Operations:

  • Random access I/O without seeking
  • Patch updates to binary files
  • Header manipulation at known offsets
  • Database-like operations with fixed positions
  • Concurrent access to different file sections

Environment & Process Operations

Environment Variables

Operation Signature stdlib Equivalent Description
GetEnv (ctx, key) os.Getenv Get environment variable

Example:

home, err := hostServiceClient.GetEnv(ctx, "HOME")
if err != nil {
    return err
}
fmt.Printf("Home directory: %s\n", home)

User Directories

Operation Signature stdlib Equivalent Description
TempDir (ctx) os.TempDir Get system temp directory
UserCacheDir (ctx) os.UserCacheDir Get user cache directory
UserConfigDir (ctx) os.UserConfigDir Get user config directory
UserHomeDir (ctx) os.UserHomeDir Get user home directory

Example:

// Get various standard directories
tempDir, _ := hostServiceClient.TempDir(ctx)
cacheDir, _ := hostServiceClient.UserCacheDir(ctx)
configDir, _ := hostServiceClient.UserConfigDir(ctx)
homeDir, _ := hostServiceClient.UserHomeDir(ctx)

fmt.Printf("Temp:   %s\n", tempDir)
fmt.Printf("Cache:  %s\n", cacheDir)
fmt.Printf("Config: %s\n", configDir)
fmt.Printf("Home:   %s\n", homeDir)

User and Group Identification

Operation Signature stdlib Equivalent Description
Getuid (ctx) os.Getuid Get real user ID
Getgid (ctx) os.Getgid Get real group ID
Geteuid (ctx) os.Geteuid Get effective user ID
Getegid (ctx) os.Getegid Get effective group ID
GetGroups (ctx) os.Getgroups Get supplementary group IDs

Example:

uid, _ := hostServiceClient.Getuid(ctx)
gid, _ := hostServiceClient.Getgid(ctx)
euid, _ := hostServiceClient.Geteuid(ctx)
egid, _ := hostServiceClient.Getegid(ctx)
groups, _ := hostServiceClient.GetGroups(ctx)

fmt.Printf("UID: %d, GID: %d\n", uid, gid)
fmt.Printf("Effective UID: %d, GID: %d\n", euid, egid)
fmt.Printf("Groups: %v\n", groups)

Process Information

Operation Signature stdlib Equivalent Description
Getpid (ctx) os.Getpid Get current process ID
Getppid (ctx) os.Getppid Get parent process ID

Example:

pid, _ := hostServiceClient.Getpid(ctx)
ppid, _ := hostServiceClient.Getppid(ctx)

fmt.Printf("Process ID: %d\n", pid)
fmt.Printf("Parent Process ID: %d\n", ppid)

Temporary Files with Automatic Cleanup

Unlike standard library temporary files, this implementation provides truly temporary resources with server-side lifecycle management. When a plugin connection closes, all temporary resources are automatically cleaned up.

Key Benefits:

  • No resource leaks from crashed plugins
  • Simplified plugin code - no manual cleanup
  • Secure isolation - each plugin's temps are tracked separately
  • Better testing - no leftover files

Example: Complete Temp File Lifecycle

// Create temp directory
tempDir, err := hostServiceClient.MkdirTemp(ctx, "", "myapp-*")
if err != nil {
    return err
}
fmt.Printf("Created temp dir: %s\n", tempDir)

// Create temp file
handle, err := hostServiceClient.FileCreateTemp(ctx, tempDir, "data-*.json")
if err != nil {
    return err
}

// Write to temp file
writer, _ := hostServiceClient.FileWriter(ctx, handle)
writer.Write([]byte(`{"status": "processing"}`))
writer.Close()

// Close handle
hostServiceClient.FileClose(ctx, handle)

// NO CLEANUP NEEDED!
// When plugin disconnects, both the temp file and directory are automatically removed

Common Patterns

Pattern: Simple File Read/Write

For small files, use unary operations:

// Read
data, err := hostServiceClient.ReadFile(ctx, "/home/user", "input.txt")

// Write
err = hostServiceClient.WriteFile(ctx, "/home/user", "output.txt", data, 0644)

Pattern: Streaming Large Files

For large files, use handle-based streaming:

// Open
handle, size, err := hostServiceClient.FileOpen(ctx, "/home/user", "large.bin", os.O_RDONLY, 0)
defer hostServiceClient.FileClose(ctx, handle)

// Stream read
stream, _ := hostServiceClient.FileReader(ctx, handle, 64*1024)
buffer := make([]byte, 4096)
for {
    n, err := stream.Read(buffer)
    if n > 0 {
        // Process buffer[:n]
    }
    if err == io.EOF {
        break
    }
}

Pattern: Directory Listing

// List all files in a directory (from filelister plugin)
entries, err := hostServiceClient.ReadDir(ctx, rootDir, path)
if err != nil {
    return err
}

var buf bytes.Buffer
for _, entry := range entries {
    if entry.IsDir() {
        buf.WriteString(fmt.Sprintf("[DIR]  %s\n", entry.Name()))
    } else {
        buf.WriteString(fmt.Sprintf("[FILE] %s\n", entry.Name()))
    }
}

// Save results
err = hostServiceClient.WriteFile(ctx, rootDir, "file_list.txt", buf.Bytes(), 0644)

Pattern: Safe File Update

// Create temp file
tmpHandle, _ := hostServiceClient.FileCreateTemp(ctx, "/home/user", ".update-*")

// Write new content
writer, _ := hostServiceClient.FileWriter(ctx, tmpHandle)
writer.Write(newData)
writer.Close()

// Get temp file path (you'll need to track this)
// Close temp handle
hostServiceClient.FileClose(ctx, tmpHandle)

// Atomic rename
err := hostServiceClient.Rename(ctx, "/home/user", tmpPath, "final.txt")

Error Handling

All operations return errors following Go conventions:

data, err := hostServiceClient.ReadFile(ctx, rootDir, path)
if err != nil {
    // Check for specific error types
    if os.IsNotExist(err) {
        return fmt.Errorf("file not found: %w", err)
    }
    if os.IsPermission(err) {
        return fmt.Errorf("permission denied: %w", err)
    }
    return fmt.Errorf("read error: %w", err)
}

Context and Cancellation

All operations accept a context for cancellation and timeouts:

// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

data, err := hostServiceClient.ReadFile(ctx, rootDir, path)
if err == context.DeadlineExceeded {
    fmt.Println("Operation timed out")
}

// With cancellation
ctx, cancel := context.WithCancel(context.Background())
go func() {
    // Cancel after some condition
    cancel()
}()

stream, err := hostServiceClient.FileReader(ctx, handle, chunkSize)

Security Considerations

All operations respect security boundaries:

  1. Path confinement - rootDir + path pattern prevents escaping boundaries
  2. Client identification - Every request is tagged with client ID
  3. Audit trails - All operations logged with client, request ID, duration
  4. Capability-based - Foundation for restricting plugin access

Example logged request:

[INFO]  host: ReadFile request from client:
  client=019b05f0-a32c-7b3d-a0f1-5087d232174f
  clientOwner=filelister
  request=019b05f0-a343-7753-93aa-117a437c5321
  root_dir=/home/user path=config.json
[INFO]  host: ReadFile completed successfully:
  client=019b05f0-a32c-7b3d-a0f1-5087d232174f
  clientOwner=filelister
  request=019b05f0-a343-7753-93aa-117a437c5321
  duration_us=245 success=true

Next Steps