199 lines
4.5 KiB
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[:])
|
|
}
|