635 lines
12 KiB
Go
635 lines
12 KiB
Go
// Package main builds the C ABI used by the KDE integration.
|
|
// Build with: go build -buildmode=c-shared -o libollie9p.so
|
|
//
|
|
// The shared 9P client implementation lives in ollie/client.
|
|
package main
|
|
|
|
/*
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"9fans.net/go/plan9"
|
|
p9client "9fans.net/go/plan9/client"
|
|
)
|
|
|
|
// Connection handle - opaque to C callers
|
|
type connHandle struct {
|
|
fsys *p9client.Fsys
|
|
}
|
|
|
|
// File handle - for streaming reads
|
|
type fidHandle struct {
|
|
conn C.int // parent connection handle
|
|
reader io.ReadCloser
|
|
}
|
|
|
|
var (
|
|
handleMu sync.Mutex
|
|
handles = make(map[C.int]*connHandle)
|
|
nextHandle C.int = 1
|
|
|
|
fidMu sync.Mutex
|
|
fids = make(map[C.int]*fidHandle)
|
|
nextFid C.int = 1
|
|
)
|
|
|
|
func allocHandle(h *connHandle) C.int {
|
|
handleMu.Lock()
|
|
defer handleMu.Unlock()
|
|
id := nextHandle
|
|
nextHandle++
|
|
handles[id] = h
|
|
return id
|
|
}
|
|
|
|
func getHandle(id C.int) *connHandle {
|
|
handleMu.Lock()
|
|
defer handleMu.Unlock()
|
|
return handles[id]
|
|
}
|
|
|
|
func freeHandle(id C.int) {
|
|
handleMu.Lock()
|
|
defer handleMu.Unlock()
|
|
delete(handles, id)
|
|
}
|
|
|
|
func allocFid(f *fidHandle) C.int {
|
|
fidMu.Lock()
|
|
defer fidMu.Unlock()
|
|
id := nextFid
|
|
nextFid++
|
|
fids[id] = f
|
|
return id
|
|
}
|
|
|
|
func getFid(id C.int) *fidHandle {
|
|
fidMu.Lock()
|
|
defer fidMu.Unlock()
|
|
return fids[id]
|
|
}
|
|
|
|
func freeFid(id C.int) {
|
|
fidMu.Lock()
|
|
defer fidMu.Unlock()
|
|
delete(fids, id)
|
|
}
|
|
|
|
// Error handling - last error per thread (simplified: global for now)
|
|
var lastError string
|
|
var lastErrMu sync.Mutex
|
|
|
|
func setError(err error) {
|
|
lastErrMu.Lock()
|
|
if err != nil {
|
|
lastError = err.Error()
|
|
} else {
|
|
lastError = ""
|
|
}
|
|
lastErrMu.Unlock()
|
|
}
|
|
|
|
//export ollie9p_last_error
|
|
func ollie9p_last_error() *C.char {
|
|
lastErrMu.Lock()
|
|
defer lastErrMu.Unlock()
|
|
if lastError == "" {
|
|
return nil
|
|
}
|
|
return C.CString(lastError)
|
|
}
|
|
|
|
//export ollie9p_free
|
|
func ollie9p_free(ptr unsafe.Pointer) {
|
|
C.free(ptr)
|
|
}
|
|
|
|
// ollie9p_connect establishes a connection to the 9P server.
|
|
// addr: socket path (e.g., "/tmp/ns.user.:0/ollie") or "tcp!host:port"
|
|
// user: username for Tauth/Tattach
|
|
// aname: attach name (identity for permissions)
|
|
// Returns: handle > 0 on success, -1 on error (call ollie9p_last_error)
|
|
//
|
|
//export ollie9p_connect
|
|
func ollie9p_connect(addr *C.char, user *C.char, aname *C.char) C.int {
|
|
goAddr := C.GoString(addr)
|
|
goUser := C.GoString(user)
|
|
goAname := C.GoString(aname)
|
|
|
|
conn, err := dial(goAddr)
|
|
if err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
fsys, err := conn.Attach(nil, goUser, goAname)
|
|
if err != nil {
|
|
conn.Close()
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
h := &connHandle{fsys: fsys}
|
|
setError(nil)
|
|
return allocHandle(h)
|
|
}
|
|
|
|
// ollie9p_connect_default connects using NAMESPACE and USER env vars.
|
|
// Returns: handle > 0 on success, -1 on error
|
|
//
|
|
//export ollie9p_connect_default
|
|
func ollie9p_connect_default() C.int {
|
|
fsys, err := dialDefault()
|
|
if err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
h := &connHandle{fsys: fsys}
|
|
setError(nil)
|
|
return allocHandle(h)
|
|
}
|
|
|
|
//export ollie9p_close
|
|
func ollie9p_close(handle C.int) {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
return
|
|
}
|
|
if h.fsys != nil {
|
|
h.fsys.Close()
|
|
}
|
|
freeHandle(handle)
|
|
}
|
|
|
|
// ollie9p_read reads a file's contents.
|
|
// Returns: newly allocated buffer (caller must ollie9p_free), NULL on error.
|
|
// outLen receives the length of returned data.
|
|
//
|
|
//export ollie9p_read
|
|
func ollie9p_read(handle C.int, path *C.char, outLen *C.int) *C.char {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return nil
|
|
}
|
|
|
|
data, err := readFile(h.fsys, C.GoString(path))
|
|
if err != nil {
|
|
setError(err)
|
|
return nil
|
|
}
|
|
|
|
setError(nil)
|
|
*outLen = C.int(len(data))
|
|
if len(data) == 0 {
|
|
return C.CString("")
|
|
}
|
|
cdata := C.CString(string(data))
|
|
return cdata
|
|
}
|
|
|
|
// ollie9p_read_bytes reads a file into a caller-provided buffer.
|
|
// Returns: bytes read, or -1 on error.
|
|
//
|
|
//export ollie9p_read_bytes
|
|
func ollie9p_read_bytes(handle C.int, path *C.char, buf *C.char, bufLen C.int) C.int {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return -1
|
|
}
|
|
|
|
data, err := readFile(h.fsys, C.GoString(path))
|
|
if err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
n := int(bufLen)
|
|
if n > len(data) {
|
|
n = len(data)
|
|
}
|
|
goBuf := unsafe.Slice((*byte)(unsafe.Pointer(buf)), n)
|
|
copy(goBuf, data[:n])
|
|
|
|
setError(nil)
|
|
return C.int(n)
|
|
}
|
|
|
|
// ollie9p_write writes data to a file.
|
|
// Returns: bytes written, or -1 on error.
|
|
//
|
|
//export ollie9p_write
|
|
func ollie9p_write(handle C.int, path *C.char, data *C.char, dataLen C.int) C.int {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return -1
|
|
}
|
|
|
|
goData := C.GoBytes(unsafe.Pointer(data), dataLen)
|
|
if err := writeFile(h.fsys, C.GoString(path), goData); err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
setError(nil)
|
|
return C.int(len(goData))
|
|
}
|
|
|
|
// ollie9p_rdwr performs write-then-read on a request/response file.
|
|
// Returns: newly allocated response buffer, NULL on error.
|
|
//
|
|
//export ollie9p_rdwr
|
|
func ollie9p_rdwr(handle C.int, path *C.char, data *C.char, dataLen C.int, outLen *C.int) *C.char {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return nil
|
|
}
|
|
|
|
goData := C.GoBytes(unsafe.Pointer(data), dataLen)
|
|
resp, err := rdwr(h.fsys, C.GoString(path), goData)
|
|
if err != nil {
|
|
setError(err)
|
|
return nil
|
|
}
|
|
|
|
setError(nil)
|
|
*outLen = C.int(len(resp))
|
|
return C.CString(string(resp))
|
|
}
|
|
|
|
// ollie9p_stat returns file metadata as "mode\tsize\tmtime\tname\n"
|
|
// Returns: newly allocated string, NULL on error.
|
|
//
|
|
//export ollie9p_stat
|
|
func ollie9p_stat(handle C.int, path *C.char) *C.char {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return nil
|
|
}
|
|
|
|
dir, err := h.fsys.Stat(C.GoString(path))
|
|
if err != nil {
|
|
setError(err)
|
|
return nil
|
|
}
|
|
|
|
setError(nil)
|
|
result := strings.Join([]string{
|
|
modeString(dir.Mode),
|
|
itoa(int64(dir.Length)),
|
|
itoa(int64(dir.Mtime)),
|
|
dir.Name,
|
|
}, "\t")
|
|
return C.CString(result)
|
|
}
|
|
|
|
// ollie9p_ls lists directory contents.
|
|
// Returns: newline-separated names, NULL on error.
|
|
//
|
|
//export ollie9p_ls
|
|
func ollie9p_ls(handle C.int, path *C.char) *C.char {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return nil
|
|
}
|
|
|
|
names, err := listDir(h.fsys, C.GoString(path))
|
|
if err != nil {
|
|
setError(err)
|
|
return nil
|
|
}
|
|
|
|
setError(nil)
|
|
return C.CString(strings.Join(names, "\n"))
|
|
}
|
|
|
|
// ollie9p_remove removes a file.
|
|
// Returns: 0 on success, -1 on error.
|
|
//
|
|
//export ollie9p_remove
|
|
func ollie9p_remove(handle C.int, path *C.char) C.int {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return -1
|
|
}
|
|
|
|
if err := h.fsys.Remove(C.GoString(path)); err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
setError(nil)
|
|
return 0
|
|
}
|
|
|
|
// ollie9p_create creates a file.
|
|
// Returns: 0 on success, -1 on error.
|
|
//
|
|
//export ollie9p_create
|
|
func ollie9p_create(handle C.int, path *C.char, perm C.uint) C.int {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return -1
|
|
}
|
|
|
|
if err := create(h.fsys, C.GoString(path), plan9.Perm(perm)); err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
setError(nil)
|
|
return 0
|
|
}
|
|
|
|
// ollie9p_mkdir creates a directory.
|
|
// Returns: 0 on success, -1 on error.
|
|
//
|
|
//export ollie9p_mkdir
|
|
func ollie9p_mkdir(handle C.int, path *C.char) C.int {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return -1
|
|
}
|
|
|
|
if err := mkdir(h.fsys, C.GoString(path)); err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
setError(nil)
|
|
return 0
|
|
}
|
|
|
|
// ollie9p_rename renames a file (Twstat).
|
|
// Returns: 0 on success, -1 on error.
|
|
//
|
|
//export ollie9p_rename
|
|
func ollie9p_rename(handle C.int, oldPath *C.char, newName *C.char) C.int {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return -1
|
|
}
|
|
|
|
if err := h.fsys.Wstat(C.GoString(oldPath), &plan9.Dir{Name: C.GoString(newName)}); err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
setError(nil)
|
|
return 0
|
|
}
|
|
|
|
// --- Streaming API ---
|
|
|
|
// ollie9p_open opens a file for streaming reads.
|
|
// Returns: fid handle > 0 on success, -1 on error.
|
|
// The fid must be closed with ollie9p_close_fid.
|
|
//
|
|
//export ollie9p_open
|
|
func ollie9p_open(handle C.int, path *C.char) C.int {
|
|
h := getHandle(handle)
|
|
if h == nil {
|
|
setError(&errString{"invalid handle"})
|
|
return -1
|
|
}
|
|
|
|
reader, err := h.fsys.Open(C.GoString(path), plan9.OREAD)
|
|
if err != nil {
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
fid := &fidHandle{
|
|
conn: handle,
|
|
reader: reader,
|
|
}
|
|
setError(nil)
|
|
return allocFid(fid)
|
|
}
|
|
|
|
// ollie9p_read_fid reads from an open file into a buffer.
|
|
// Returns: bytes read (0 on EOF), -1 on error.
|
|
// Blocks until data is available or EOF.
|
|
//
|
|
//export ollie9p_read_fid
|
|
func ollie9p_read_fid(fid C.int, buf *C.char, bufLen C.int) C.int {
|
|
f := getFid(fid)
|
|
if f == nil {
|
|
setError(&errString{"invalid fid"})
|
|
return -1
|
|
}
|
|
|
|
goBuf := unsafe.Slice((*byte)(unsafe.Pointer(buf)), int(bufLen))
|
|
n, err := f.reader.Read(goBuf)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
setError(nil)
|
|
return 0
|
|
}
|
|
setError(err)
|
|
return -1
|
|
}
|
|
|
|
setError(nil)
|
|
return C.int(n)
|
|
}
|
|
|
|
// ollie9p_close_fid closes an open file handle.
|
|
//
|
|
//export ollie9p_close_fid
|
|
func ollie9p_close_fid(fid C.int) {
|
|
f := getFid(fid)
|
|
if f == nil {
|
|
return
|
|
}
|
|
if f.reader != nil {
|
|
f.reader.Close()
|
|
}
|
|
freeFid(fid)
|
|
}
|
|
|
|
func dial(address string) (*p9client.Conn, error) {
|
|
network := "unix"
|
|
dialAddr := address
|
|
if strings.HasPrefix(address, "tcp!") {
|
|
network = "tcp"
|
|
dialAddr = strings.TrimPrefix(address, "tcp!")
|
|
} else if strings.HasPrefix(address, "unix!") {
|
|
dialAddr = strings.TrimPrefix(address, "unix!")
|
|
} else if strings.Contains(address, ":") && !strings.HasPrefix(address, "/") {
|
|
network = "tcp"
|
|
}
|
|
return p9client.Dial(network, dialAddr)
|
|
}
|
|
|
|
func dialDefault() (*p9client.Fsys, error) {
|
|
ns := os.Getenv("NAMESPACE")
|
|
if ns == "" {
|
|
ns = p9client.Namespace()
|
|
}
|
|
if ns == "" {
|
|
return nil, fmt.Errorf("cannot determine namespace")
|
|
}
|
|
user := os.Getenv("USER")
|
|
if user == "" {
|
|
user = "none"
|
|
}
|
|
aname := os.Getenv("OLLIE_UNAME")
|
|
if aname == "" {
|
|
aname = user
|
|
}
|
|
conn, err := dial(ns + "/ollie")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fsys, err := conn.Attach(nil, user, aname)
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
return fsys, nil
|
|
}
|
|
|
|
func readFile(fsys *p9client.Fsys, path string) ([]byte, error) {
|
|
fid, err := fsys.Open(path, plan9.OREAD)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer fid.Close()
|
|
return io.ReadAll(fid)
|
|
}
|
|
|
|
func writeFile(fsys *p9client.Fsys, path string, data []byte) error {
|
|
fid, err := fsys.Open(path, plan9.OWRITE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := fid.Write(data); err != nil {
|
|
fid.Close()
|
|
return err
|
|
}
|
|
return fid.Close()
|
|
}
|
|
|
|
func rdwr(fsys *p9client.Fsys, path string, data []byte) ([]byte, error) {
|
|
fid, err := fsys.Open(path, plan9.ORDWR)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := fid.Write(data); err != nil {
|
|
fid.Close()
|
|
return nil, err
|
|
}
|
|
if _, err := fid.Seek(0, 0); err != nil {
|
|
fid.Close()
|
|
return nil, err
|
|
}
|
|
result, err := io.ReadAll(fid)
|
|
closeErr := fid.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, closeErr
|
|
}
|
|
|
|
func create(fsys *p9client.Fsys, path string, perm plan9.Perm) error {
|
|
fid, err := fsys.Create(path, plan9.OWRITE, perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fid.Close()
|
|
}
|
|
|
|
func mkdir(fsys *p9client.Fsys, path string) error {
|
|
fid, err := fsys.Create(path, plan9.OREAD, plan9.DMDIR|0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fid.Close()
|
|
}
|
|
|
|
func listDir(fsys *p9client.Fsys, path string) ([]string, error) {
|
|
fid, err := fsys.Open(path, plan9.OREAD)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dirs, err := fid.Dirreadall()
|
|
fid.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
names := make([]string, len(dirs))
|
|
for i, dir := range dirs {
|
|
names[i] = dir.Name
|
|
}
|
|
return names, nil
|
|
}
|
|
|
|
func modeString(mode plan9.Perm) string {
|
|
var buf [10]byte
|
|
if mode&plan9.DMDIR != 0 {
|
|
buf[0] = 'd'
|
|
} else {
|
|
buf[0] = '-'
|
|
}
|
|
const rwx = "rwx"
|
|
for i := 0; i < 3; i++ {
|
|
for j := 0; j < 3; j++ {
|
|
if mode&(1<<uint(8-i*3-j)) != 0 {
|
|
buf[1+i*3+j] = rwx[j]
|
|
} else {
|
|
buf[1+i*3+j] = '-'
|
|
}
|
|
}
|
|
}
|
|
return string(buf[:])
|
|
}
|
|
|
|
// Helper types
|
|
type errString struct{ s string }
|
|
|
|
func (e *errString) Error() string { return e.s }
|
|
|
|
func itoa(n int64) string {
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
var buf [20]byte
|
|
i := len(buf)
|
|
neg := n < 0
|
|
if neg {
|
|
n = -n
|
|
}
|
|
for n > 0 {
|
|
i--
|
|
buf[i] = byte('0' + n%10)
|
|
n /= 10
|
|
}
|
|
if neg {
|
|
i--
|
|
buf[i] = '-'
|
|
}
|
|
return string(buf[i:])
|
|
}
|
|
|
|
func main() {}
|