// Package lib9p provides a C-compatible shared library for 9P client operations. // Build with: go build -buildmode=c-shared -o libollie9p.so // // This is the single 9P client implementation used by: // - ollie-9p CLI tool // - KDE GUI (via C bindings) // - Any other C/C++ integration package main /* #include #include */ import "C" import ( "io" "strings" "sync" "unsafe" "9fans.net/go/plan9" client "ollie/lib9p/client" ) // Connection handle - opaque to C callers type connHandle struct { fsys *client.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) fsys, err := client.Dial(goAddr, goUser, goAname) if err != nil { 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 := client.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 := h.fsys.Read(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 := h.fsys.Read(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 := h.fsys.Write(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 := h.fsys.Rdwr(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{ client.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 := h.fsys.Ls(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 := h.fsys.Create(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 := h.fsys.Mkdir(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.Rename(C.GoString(oldPath), 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.OpenRead(C.GoString(path)) 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) } // 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() {}