// 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 #include */ 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< 0 { i-- buf[i] = byte('0' + n%10) n /= 10 } if neg { i-- buf[i] = '-' } return string(buf[i:]) } func main() {}