lib9p: shared library for 9P client operations
Go shared library (c-shared buildmode) that wraps 9fans.net/go/plan9/client. Provides C API for persistent 9P connections, eliminating process spawn overhead for GUI operations. Exports: ollie9p_connect, ollie9p_read, ollie9p_write, ollie9p_rdwr, ollie9p_ls, ollie9p_stat, ollie9p_remove, ollie9p_create, ollie9p_mkdir, ollie9p_rename, ollie9p_close, ollie9p_last_error, ollie9p_free Build: go build -buildmode=c-shared -o libollie9p.so Install: just lib9p (installs to ~/.local/lib and ~/.local/include)
This commit is contained in:
parent
6d902ba7e9
commit
b5c67e1a93
13
justfile
13
justfile
|
|
@ -11,6 +11,8 @@ set dotenv-load := false
|
||||||
home := env_var('HOME')
|
home := env_var('HOME')
|
||||||
cfg := home / ".config/ollie"
|
cfg := home / ".config/ollie"
|
||||||
bin := home / "bin"
|
bin := home / "bin"
|
||||||
|
lib := home / ".local/lib"
|
||||||
|
inc := home / ".local/include"
|
||||||
|
|
||||||
# === Default (KF6) path ===
|
# === Default (KF6) path ===
|
||||||
default: build install
|
default: build install
|
||||||
|
|
@ -20,9 +22,9 @@ kf5: build-kf5 install-kf5
|
||||||
|
|
||||||
# === Build targets ===
|
# === Build targets ===
|
||||||
|
|
||||||
build: core ninep ollie-remote acme lsp-tools kde
|
build: core ninep lib9p ollie-remote acme lsp-tools kde
|
||||||
|
|
||||||
build-kf5: core ninep ollie-remote acme kde-kf5
|
build-kf5: core ninep lib9p ollie-remote acme kde-kf5
|
||||||
|
|
||||||
core:
|
core:
|
||||||
@echo === Building ollie-core ===
|
@echo === Building ollie-core ===
|
||||||
|
|
@ -33,6 +35,13 @@ ninep:
|
||||||
go build -o {{bin}}/olliesrv ./cmd/olliesrv/
|
go build -o {{bin}}/olliesrv ./cmd/olliesrv/
|
||||||
go build -o {{bin}}/ollie-9p ./cmd/ollie-9p/
|
go build -o {{bin}}/ollie-9p ./cmd/ollie-9p/
|
||||||
|
|
||||||
|
lib9p:
|
||||||
|
@echo === Building lib9p ===
|
||||||
|
cd lib9p && go build -buildmode=c-shared -o libollie9p.so
|
||||||
|
mkdir -p {{lib}} {{inc}}
|
||||||
|
install -m755 lib9p/libollie9p.so {{lib}}/libollie9p.so
|
||||||
|
install -m644 lib9p/libollie9p.h {{inc}}/libollie9p.h
|
||||||
|
|
||||||
acme:
|
acme:
|
||||||
@echo === Building ollie-acme ===
|
@echo === Building ollie-acme ===
|
||||||
go build -o {{bin}}/Ollie ./cmd/Ollie/
|
go build -o {{bin}}/Ollie ./cmd/Ollie/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Build artifacts
|
||||||
|
*.so
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
test
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
module ollie/lib9p
|
||||||
|
|
||||||
|
go 1.22
|
||||||
|
|
||||||
|
require 9fans.net/go v0.0.5
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
9fans.net/go v0.0.5 h1:u0H3Et5NowdVm56FDD+aWcDOao9zEo+zolNFsr8qyQk=
|
||||||
|
9fans.net/go v0.0.5/go.mod h1:Rxvbbc1e+1TyGMjAvLthGTyO97t+6JMQ6ly+Lcs9Uf0=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||||
|
golang.org/x/exp v0.0.0-20210405174845-4513512abef3/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||||
|
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
@ -0,0 +1,513 @@
|
||||||
|
// 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 <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"9fans.net/go/plan9"
|
||||||
|
"9fans.net/go/plan9/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connection handle - opaque to C callers
|
||||||
|
type connHandle struct {
|
||||||
|
conn *client.Conn
|
||||||
|
fsys *client.Fsys
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
handleMu sync.Mutex
|
||||||
|
handles = make(map[C.int]*connHandle)
|
||||||
|
nextHandle 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
network := "unix"
|
||||||
|
dialAddr := goAddr
|
||||||
|
if strings.HasPrefix(goAddr, "tcp!") {
|
||||||
|
network = "tcp"
|
||||||
|
dialAddr = strings.TrimPrefix(goAddr, "tcp!")
|
||||||
|
} else if strings.HasPrefix(goAddr, "unix!") {
|
||||||
|
dialAddr = strings.TrimPrefix(goAddr, "unix!")
|
||||||
|
} else if strings.Contains(goAddr, ":") && !strings.HasPrefix(goAddr, "/") {
|
||||||
|
network = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := client.Dial(network, dialAddr)
|
||||||
|
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{conn: conn, 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 {
|
||||||
|
ns := os.Getenv("NAMESPACE")
|
||||||
|
if ns == "" {
|
||||||
|
ns = client.Namespace()
|
||||||
|
}
|
||||||
|
if ns == "" {
|
||||||
|
setError(&errString{"cannot determine namespace"})
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
addr := ns + "/ollie"
|
||||||
|
|
||||||
|
user := os.Getenv("USER")
|
||||||
|
if user == "" {
|
||||||
|
user = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
aname := os.Getenv("OLLIE_UNAME")
|
||||||
|
if aname == "" {
|
||||||
|
aname = user
|
||||||
|
}
|
||||||
|
|
||||||
|
cAddr := C.CString(addr)
|
||||||
|
cUser := C.CString(user)
|
||||||
|
cAname := C.CString(aname)
|
||||||
|
defer C.free(unsafe.Pointer(cAddr))
|
||||||
|
defer C.free(unsafe.Pointer(cUser))
|
||||||
|
defer C.free(unsafe.Pointer(cAname))
|
||||||
|
|
||||||
|
return ollie9p_connect(cAddr, cUser, cAname)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export ollie9p_close
|
||||||
|
func ollie9p_close(handle C.int) {
|
||||||
|
h := getHandle(handle)
|
||||||
|
if h == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h.conn != nil {
|
||||||
|
h.conn.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
|
||||||
|
}
|
||||||
|
|
||||||
|
fid, err := h.fsys.Open(C.GoString(path), plan9.OREAD)
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer fid.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(fid)
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(nil)
|
||||||
|
*outLen = C.int(len(data))
|
||||||
|
if len(data) == 0 {
|
||||||
|
return C.CString("")
|
||||||
|
}
|
||||||
|
// Allocate C memory and copy
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fid, err := h.fsys.Open(C.GoString(path), plan9.OREAD)
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
defer fid.Close()
|
||||||
|
|
||||||
|
// Read into Go slice backed by C memory
|
||||||
|
goBuf := unsafe.Slice((*byte)(unsafe.Pointer(buf)), int(bufLen))
|
||||||
|
n, err := io.ReadFull(fid, goBuf)
|
||||||
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
|
setError(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fid, err := h.fsys.Open(C.GoString(path), plan9.OWRITE)
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
goData := C.GoBytes(unsafe.Pointer(data), dataLen)
|
||||||
|
n, err := fid.Write(goData)
|
||||||
|
if err != nil {
|
||||||
|
fid.Close()
|
||||||
|
setError(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fid.Close(); err != nil {
|
||||||
|
setError(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(nil)
|
||||||
|
return C.int(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
fid, err := h.fsys.Open(C.GoString(path), plan9.ORDWR)
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
goData := C.GoBytes(unsafe.Pointer(data), dataLen)
|
||||||
|
if _, err := fid.Write(goData); err != nil {
|
||||||
|
fid.Close()
|
||||||
|
setError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to beginning
|
||||||
|
if _, err := fid.Seek(0, 0); err != nil {
|
||||||
|
fid.Close()
|
||||||
|
setError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
resp, err := io.ReadAll(fid)
|
||||||
|
fid.Close()
|
||||||
|
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)
|
||||||
|
// Return tab-separated: mode, size, mtime, name
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fid, err := h.fsys.Open(C.GoString(path), plan9.OREAD)
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer fid.Close()
|
||||||
|
|
||||||
|
dirs, err := fid.Dirreadall()
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
for _, d := range dirs {
|
||||||
|
names = append(names, d.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fid, err := h.fsys.Create(C.GoString(path), plan9.OWRITE, plan9.Perm(perm))
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
fid.Close()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fid, err := h.fsys.Create(C.GoString(path), plan9.OREAD, plan9.DMDIR|0755)
|
||||||
|
if err != nil {
|
||||||
|
setError(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
fid.Close()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper types
|
||||||
|
type errString struct{ s string }
|
||||||
|
|
||||||
|
func (e *errString) Error() string { return e.s }
|
||||||
|
|
||||||
|
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[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Quick test of libollie9p
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "libollie9p.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("Connecting to olliesrv...\n");
|
||||||
|
int handle = ollie9p_connect_default();
|
||||||
|
if (handle < 0) {
|
||||||
|
char *err = ollie9p_last_error();
|
||||||
|
printf("Connect failed: %s\n", err ? err : "(unknown)");
|
||||||
|
if (err) ollie9p_free(err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Connected, handle=%d\n", handle);
|
||||||
|
|
||||||
|
// Read session/idx
|
||||||
|
int len = 0;
|
||||||
|
char *data = ollie9p_read(handle, "session/idx", &len);
|
||||||
|
if (data) {
|
||||||
|
printf("session/idx (%d bytes):\n%s\n", len, data);
|
||||||
|
ollie9p_free(data);
|
||||||
|
} else {
|
||||||
|
char *err = ollie9p_last_error();
|
||||||
|
printf("Read failed: %s\n", err ? err : "(unknown)");
|
||||||
|
if (err) ollie9p_free(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// List session directory
|
||||||
|
char *ls = ollie9p_ls(handle, "session");
|
||||||
|
if (ls) {
|
||||||
|
printf("session/ listing:\n%s\n", ls);
|
||||||
|
ollie9p_free(ls);
|
||||||
|
}
|
||||||
|
|
||||||
|
ollie9p_close(handle);
|
||||||
|
printf("Done.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue