ollie/lib9p/client/client.go

199 lines
4.5 KiB
Go

package lib9p
import (
"fmt"
"io"
"os"
"strings"
"9fans.net/go/plan9"
p9client "9fans.net/go/plan9/client"
)
// Fsys is an attached 9P filesystem handle.
type Fsys struct {
fsys *p9client.Fsys
}
// Dial connects to the 9P server at the given address and attaches with the
// specified user identity and attach name. Address format: "unix!/path" or
// "/path" for unix sockets, "tcp!host:port" or "host:port" for TCP.
func Dial(address, user, aname string) (*Fsys, 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"
}
conn, err := p9client.Dial(network, dialAddr)
if err != nil {
return nil, err
}
fsys, err := conn.Attach(nil, user, aname)
if err != nil {
conn.Close()
return nil, err
}
return &Fsys{fsys: fsys}, nil
}
// DialDefault connects using NAMESPACE env var (falls back to plan9 client
// namespace) and USER/OLLIE_UNAME for identity.
func DialDefault() (*Fsys, error) {
ns := os.Getenv("NAMESPACE")
if ns == "" {
ns = p9client.Namespace()
}
if ns == "" {
return nil, fmt.Errorf("cannot determine namespace; use -a flag or set $NAMESPACE")
}
address := ns + "/ollie"
user := os.Getenv("USER")
if user == "" {
user = "none"
}
aname := os.Getenv("OLLIE_UNAME")
if aname == "" {
aname = user
}
return Dial(address, user, aname)
}
// Close releases the filesystem attachment.
func (f *Fsys) Close() error {
return f.fsys.Close()
}
// Read reads a file's contents.
func (f *Fsys) Read(path string) ([]byte, error) {
fid, err := f.fsys.Open(path, plan9.OREAD)
if err != nil {
return nil, err
}
defer fid.Close()
return io.ReadAll(fid)
}
// OpenRead opens a file for reading and returns an io.ReadCloser.
// The caller is responsible for closing it. Used for streaming reads
// where data should be consumed incrementally.
func (f *Fsys) OpenRead(path string) (io.ReadCloser, error) {
fid, err := f.fsys.Open(path, plan9.OREAD)
if err != nil {
return nil, err
}
return fid, nil
}
// Write writes data to a file. Opens OWRITE, writes, then closes (flush).
func (f *Fsys) Write(path string, data []byte) error {
fid, err := f.fsys.Open(path, plan9.OWRITE)
if err != nil {
return err
}
if _, err := fid.Write(data); err != nil {
fid.Close()
return err
}
return fid.Close()
}
// Rdwr performs write-then-read on a request/response file.
func (f *Fsys) Rdwr(path string, data []byte) ([]byte, error) {
fid, err := f.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
}
resp, err := io.ReadAll(fid)
fid.Close()
if err != nil {
return nil, err
}
return resp, nil
}
// Stat returns file metadata.
func (f *Fsys) Stat(path string) (*plan9.Dir, error) {
return f.fsys.Stat(path)
}
// Ls lists directory contents, returning entry names.
func (f *Fsys) Ls(path string) ([]string, error) {
fid, err := f.fsys.Open(path, plan9.OREAD)
if err != nil {
return nil, err
}
defer fid.Close()
dirs, err := fid.Dirreadall()
if err != nil {
return nil, err
}
names := make([]string, len(dirs))
for i, d := range dirs {
names[i] = d.Name
}
return names, nil
}
// Create creates a file with the given permissions.
func (f *Fsys) Create(path string, perm plan9.Perm) error {
fid, err := f.fsys.Create(path, plan9.OWRITE, perm)
if err != nil {
return err
}
return fid.Close()
}
// Remove removes a file.
func (f *Fsys) Remove(path string) error {
return f.fsys.Remove(path)
}
// Mkdir creates a directory.
func (f *Fsys) Mkdir(path string) error {
fid, err := f.fsys.Create(path, plan9.OREAD, plan9.DMDIR|0755)
if err != nil {
return err
}
return fid.Close()
}
// Rename renames a file (Twstat with new name).
func (f *Fsys) Rename(oldPath, newName string) error {
return f.fsys.Wstat(oldPath, &plan9.Dir{Name: newName})
}
// ModeString returns a human-readable permission string like "-rw-r--r--".
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[:])
}