diff --git a/acme/acme.go b/acme/acme.go index ef2a193..96c9c3b 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -56,10 +56,6 @@ var fsys *client.Fsys var fsysErr error var fsysOnce sync.Once -func mountAcme() { - fsys, fsysErr = client.MountService("acme") -} - // AutoExit sets whether to call os.Exit the next time the last managed acme window is deleted. // If there are no acme windows at the time of the call, the exit does not happen until one // is created and then deleted. diff --git a/acme/acme_p9p.go b/acme/acme_p9p.go new file mode 100644 index 0000000..862f817 --- /dev/null +++ b/acme/acme_p9p.go @@ -0,0 +1,11 @@ +// +build !plan9 + +package acme + +import "9fans.net/go/plan9/client" + +func mountAcme() { + fs, err := client.MountService("acme") + fsys = fs + fsysErr = err +} diff --git a/acme/acme_plan9.go b/acme/acme_plan9.go new file mode 100644 index 0000000..c5734cd --- /dev/null +++ b/acme/acme_plan9.go @@ -0,0 +1,11 @@ +package acme + +import ( + "9fans.net/go/plan9/client" +) + +func mountAcme() { + // Already mounted at /mnt/acme + fsys = &client.Fsys{Mtpt: "/mnt/acme"} + fsysErr = nil +} diff --git a/draw/alloc.go b/draw/alloc.go index 813cbbe..7741f76 100644 --- a/draw/alloc.go +++ b/draw/alloc.go @@ -2,7 +2,9 @@ package draw import ( "fmt" + "os" "runtime" + "strings" ) // AllocImage allocates a new Image on display d. @@ -28,7 +30,6 @@ import ( // used to paint a region red: // // red, err := display.AllocImage(draw.Rect(0, 0, 1, 1), draw.RGB24, true, draw.Red) -// func (d *Display) AllocImage(r Rectangle, pix Pix, repl bool, color Color) (*Image, error) { d.mu.Lock() defer d.mu.Unlock() @@ -104,11 +105,68 @@ func allocImage(d *Display, ai *Image, r Rectangle, pix Pix, repl bool, val Colo return i, nil } -/* -func namedimage(d *Display, name string) (*Image, nil) { - panic("namedimage") +func namedImage(d *Display, ai *Image, name string) (i *Image, err error) { + n := len(name) + if n >= 256 { + return nil, fmt.Errorf("namedImage: name too long") + } + // flush pending data so we don't get error allocating the image + d.flush(false) + a := d.bufimage(1 + 4 + 1 + n) + d.imageid++ + id := d.imageid + a[0] = 'n' + bplong(a[1:], id) + a[5] = byte(n) + copy(a[6:], name) + if err := d.flush(false); err != nil { + fmt.Fprintf(os.Stderr, "namedImage: %v\n", err) + return nil, err + } + + a = d.bufimage(1) + a[0] = 'I' + if err := d.flush(false); err != nil { + fmt.Fprintf(os.Stderr, "cannot read image info: %v\n", err) + return nil, err + } + info := make([]byte, 12*12) + n, err = d.conn.ReadDraw(info) + if err != nil { + return nil, err + } + if n < len(info) { + return nil, fmt.Errorf("short info from rddraw") + } + + pix, err := ParsePix(strings.TrimSpace(string(info[2*12 : 3*12]))) + if err != nil { + a := d.bufimage(1 + 4) + a[0] = 'f' + bplong(a[1:], id) + d.flush(false) + return nil, fmt.Errorf("bad channel %q from devdraw", info[2*12:3*12]) + } + i = ai + if i == nil { + i = new(Image) + } + *i = Image{ + Display: d, + id: id, + Pix: pix, + Depth: pix.Depth(), + Repl: atoi(info[3*12:]) > 0, + R: ator(info[4*12:]), + Clipr: ator(info[8*12:]), + Screen: nil, + next: nil, + } + runtime.SetFinalizer(i, (*Image).Free) + return i, nil } +/* func nameimage(i *Image, name string, in bool) error { a := i.Display.bufimage(1+4+1+1+len(name)) a[0] = 'N' diff --git a/draw/drawfcall/mux.go b/draw/drawfcall/mux.go index 4e289de..a7ff07a 100644 --- a/draw/drawfcall/mux.go +++ b/draw/drawfcall/mux.go @@ -1,3 +1,5 @@ +// +build !plan9 + package drawfcall import ( diff --git a/draw/drawfcall/mux_plan9.go b/draw/drawfcall/mux_plan9.go new file mode 100644 index 0000000..6849bb8 --- /dev/null +++ b/draw/drawfcall/mux_plan9.go @@ -0,0 +1,264 @@ +package drawfcall + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "image" + "io/ioutil" + "os" + "strconv" + "strings" + "sync" +) + +type Conn struct { + ctl *os.File + data *os.File + cons *bufio.Reader + consctl *os.File + mouse *os.File + snarf *os.File + cursor *os.File + n int // connection number + initCtl []byte + oldLabel string + + readData []byte + ctlWasRead bool + lk sync.Mutex +} + +func New() (*Conn, error) { + ctl, err := os.OpenFile("/dev/draw/new", os.O_RDWR, 0) + if err != nil { + return nil, err + } + + var b [12*12 + 1]byte + nr, err := ctl.Read(b[:]) + if err != nil { + return nil, err + } + f := strings.Fields(string(b[:nr])) + if len(f) != 12 { + return nil, fmt.Errorf("bad ctl file") + } + n, err := strconv.Atoi(f[0]) + if err != nil { + return nil, err + } + + data, err := os.OpenFile(fmt.Sprintf("/dev/draw/%v/data", n), os.O_RDWR, 0) + if err != nil { + return nil, err + } + cons, err := os.Open("/dev/cons") + if err != nil { + return nil, err + } + + consctl, err := os.OpenFile("/dev/consctl", os.O_WRONLY, 0) + if err != nil { + return nil, err + } + _, err = consctl.WriteString("rawon") + if err != nil { + return nil, err + } + + mouse, err := os.OpenFile("/dev/mouse", os.O_RDWR, 0) + if err != nil { + return nil, err + } + snarf, err := os.Open("/dev/snarf") + if err != nil { + return nil, err + } + cursor, err := os.OpenFile("/dev/cursor", os.O_WRONLY, 0) + if err != nil { + return nil, err + } + + return &Conn{ + ctl: ctl, + data: data, + cons: bufio.NewReader(cons), + consctl: consctl, + mouse: mouse, + snarf: snarf, + cursor: cursor, + initCtl: b[:nr], + n: n, + }, nil +} + +func (c *Conn) Close() error { + return c.ctl.Close() +} + +func (c *Conn) Init(label, winsize string) error { + if b, err := ioutil.ReadFile("/dev/label"); err == nil { + c.oldLabel = string(b) + } + // Ignore error because we may not be running in rio + ioutil.WriteFile("/dev/label", []byte(label), 0600) + return nil +} + +func atoi(s string) (n int) { + n, _ = strconv.Atoi(s) + return +} + +func (c *Conn) ReadMouse() (m Mouse, resized bool, err error) { + var buf [1 + 5*12]byte + var nr int + + nr, err = c.mouse.Read(buf[:]) + if err != nil { + return + } + f := strings.Fields(string(buf[:nr])) + if len(f) != 5 { + err = errors.New("bad mouse event") + return + } + m.Point = image.Pt(atoi(f[1]), atoi(f[2])) + m.Buttons = atoi(f[3]) + m.Msec = atoi(f[4]) + if f[0] == "r" { + resized = true + } + return +} + +func (c *Conn) ReadKbd() (r rune, err error) { + r, _, err = c.cons.ReadRune() + return +} + +func (c *Conn) MoveTo(p image.Point) error { + _, err := fmt.Fprintf(c.mouse, "m%11d %11d ", p.X, p.Y) + return err +} + +func (c *Conn) Cursor(cursor *Cursor) error { + if cursor == nil { + // Revert to default cursor (Arrow) + _, err := c.cursor.Write([]byte{0}) + return err + } + b := make([]byte, 2*4+len(cursor.Clr)+len(cursor.Set)) + i := 0 + binary.LittleEndian.PutUint32(b[i:], uint32(cursor.Point.X)) + i += 4 + binary.LittleEndian.PutUint32(b[i:], uint32(cursor.Point.Y)) + i += 4 + i += copy(b[i:], cursor.Clr[:]) + i += copy(b[i:], cursor.Set[:]) + _, err := c.cursor.Write(b) + return err +} + +func (c *Conn) BounceMouse(m *Mouse) error { + panic("unimplemented") +} + +func (c *Conn) Label(label string) error { + panic("unimplemented") +} + +// Return values are bytes copied, actual size, error. +func (c *Conn) ReadSnarf(b []byte) (int, int, error) { + _, err := c.snarf.Seek(0, 0) + if err != nil { + return 0, 0, err + } + n, err := c.snarf.Read(b) + return n, n, err +} + +func (c *Conn) WriteSnarf(snarf []byte) error { + // /dev/snarf updates when the file is closed, so we must open it for each call + f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0) + if err != nil { + return err + } + _, err = f.Write(snarf) + if err != nil { + return err + } + return f.Close() +} + +func (c *Conn) Top() error { + panic("unimplemented") +} + +func (c *Conn) Resize(r image.Rectangle) error { + panic("unimplemented") +} + +func (c *Conn) ReadDraw(b []byte) (n int, err error) { + c.lk.Lock() + if len(c.readData) > 0 { + n = copy(b, c.readData) + c.readData = c.readData[n:] + c.lk.Unlock() + return n, nil + } + c.lk.Unlock() + return c.data.Read(b[:]) +} + +func bplong(b []byte, n uint32) { + binary.LittleEndian.PutUint32(b, n) +} + +func (c *Conn) WriteDraw(b []byte) (int, error) { + i := 0 +Loop: + for i < len(b) { + switch b[i] { + case 'J': // set image 0 to screen image + i++ + + case 'I': // get image info: 'I' + c.lk.Lock() + if !c.ctlWasRead { + c.readData = append(c.readData, c.initCtl...) + c.ctlWasRead = true + } else { + b := make([]byte, 12*12) + n, err := c.ctl.Read(b) + if err != nil { + c.lk.Unlock() + return 0, err + } + c.readData = append(c.readData, b[:n]...) + } + c.lk.Unlock() + i++ + + case 'q': // query: 'Q' n[1] queryspec[n] + if bytes.Equal(b, []byte{'q', 1, 'd'}) { + dpi := fmt.Sprintf("%12d", 100) + c.lk.Lock() + c.readData = append(c.readData, []byte(dpi)...) + c.lk.Unlock() + } + i += 1 + 1 + int(b[1]) + + default: + break Loop + } + } + if len(b[i:]) == 0 { + return i, nil + } + n, err := c.data.Write(b[i:]) + return n + i, err +} diff --git a/draw/init.go b/draw/init.go index c124c57..b5a654b 100644 --- a/draw/init.go +++ b/draw/init.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "runtime" "strings" "sync" @@ -324,13 +325,17 @@ func (d *Display) getimage0(i *Image) (*Image, error) { func (d *Display) Attach(ref int) error { d.mu.Lock() defer d.mu.Unlock() - oi := d.Image - i, err := d.getimage0(oi) - if err != nil { - return err + if runtime.GOOS != "plan9" { + oi := d.Image + i, err := d.getimage0(oi) + if err != nil { + return err + } + d.Image = i } - d.Image = i + i := d.Image d.Screen.free() + var err error d.Screen, err = i.allocScreen(d.White, false) if err != nil { return err diff --git a/draw/window.go b/draw/window.go index 2b3fe7c..1b94ca2 100644 --- a/draw/window.go +++ b/draw/window.go @@ -1,6 +1,10 @@ package draw -import "fmt" +import ( + "fmt" + "io/ioutil" + "runtime" +) var screenid uint32 @@ -80,7 +84,20 @@ func (s *Screen) free() error { func allocwindow(i *Image, s *Screen, r Rectangle, ref int, val Color) (*Image, error) { d := s.Display - i, err := allocImage(d, i, r, d.ScreenImage.Pix, false, val, s.id, ref) + var err error + if runtime.GOOS == "plan9" { + const BorderWidth = 4 + name, err := ioutil.ReadFile("/dev/winname") + if err != nil { + return nil, err + } + i, err = namedImage(d, i, string(name)) + if err == nil { + i.R = i.R.Inset(BorderWidth) + } + } else { + i, err = allocImage(d, i, r, d.ScreenImage.Pix, false, val, s.id, ref) + } if err != nil { return nil, err } diff --git a/plan9/client/conn.go b/plan9/client/conn.go index 6d654f6..2b520a6 100644 --- a/plan9/client/conn.go +++ b/plan9/client/conn.go @@ -1,3 +1,6 @@ +//go:build !plan9 +// +build !plan9 + package client // import "9fans.net/go/plan9/client" import ( @@ -137,7 +140,7 @@ func (c *conn) newFid(fid uint32, qid plan9.Qid) *Fid { func (c *conn) newfidnum() (uint32, error) { c.x.Lock() defer c.x.Unlock() - for fidnum, _ := range c.freefid { + for fidnum := range c.freefid { delete(c.freefid, fidnum) return fidnum, nil } @@ -159,7 +162,7 @@ func (c *conn) newtag(ch chan *plan9.Fcall) (uint16, error) { c.x.Lock() defer c.x.Unlock() var tagnum uint16 - for tagnum, _ = range c.freetag { + for tagnum = range c.freetag { delete(c.freetag, tagnum) goto found } diff --git a/plan9/client/conn_plan9.go b/plan9/client/conn_plan9.go new file mode 100644 index 0000000..6231933 --- /dev/null +++ b/plan9/client/conn_plan9.go @@ -0,0 +1,6 @@ +package client + +type Conn struct { + fd int + name string +} diff --git a/plan9/client/dial.go b/plan9/client/dial.go index a02f8ff..0a3f058 100644 --- a/plan9/client/dial.go +++ b/plan9/client/dial.go @@ -1,3 +1,5 @@ +// +build !plan9 + package client import ( diff --git a/plan9/client/dial_plan9.go b/plan9/client/dial_plan9.go new file mode 100644 index 0000000..f164ae4 --- /dev/null +++ b/plan9/client/dial_plan9.go @@ -0,0 +1,32 @@ +package client + +import ( + "path/filepath" + "syscall" +) + +func openSrv(service string) (fd int, err error) { + p := filepath.Join(Namespace(), service) + return syscall.Open(p, syscall.O_RDWR) +} + +func DialService(service string) (*Conn, error) { + fd, err := openSrv(service) + if err != nil { + return nil, err + } + return &Conn{fd: fd, name: service}, nil +} + +func Mount(network, addr string) (*Fsys, error) { + panic("unimplemented") +} + +func MountService(service string) (*Fsys, error) { + panic("unimplemented") +} + +// Namespace returns the path to the name space directory. +func Namespace() string { + return "/srv" +} diff --git a/plan9/client/fid.go b/plan9/client/fid.go index eb0a545..756e9ed 100644 --- a/plan9/client/fid.go +++ b/plan9/client/fid.go @@ -2,80 +2,10 @@ package client import ( "io" - "os" - "strings" - "sync" "9fans.net/go/plan9" ) -func getuser() string { return os.Getenv("USER") } - -type Fid struct { - qid plan9.Qid - fid uint32 - mode uint8 - // f guards offset and c. - f sync.Mutex - // c holds the underlying connection. - // It's nil after the Fid has been closed. - _c *conn - offset int64 -} - -func (fid *Fid) conn() (*conn, error) { - fid.f.Lock() - c := fid._c - fid.f.Unlock() - if c == nil { - return nil, errClosed - } - return c, nil -} - -func (fid *Fid) Close() error { - if fid == nil { - // TODO why is Close allowed on a nil fid but no other operations? - return nil - } - conn, err := fid.conn() - if err != nil { - return err - } - tx := &plan9.Fcall{Type: plan9.Tclunk, Fid: fid.fid} - _, err = conn.rpc(tx, fid) - return err -} - -// clunked marks the fid as clunked and closes it. This is called -// just before sending a message that will clunk it. -func (fid *Fid) clunked() error { - fid.f.Lock() - defer fid.f.Unlock() - if fid._c == nil { - return errClosed - } - fid._c.putfidnum(fid.fid) - fid._c.release() - fid._c = nil - return nil -} - -func (fid *Fid) Create(name string, mode uint8, perm plan9.Perm) error { - conn, err := fid.conn() - if err != nil { - return err - } - tx := &plan9.Fcall{Type: plan9.Tcreate, Fid: fid.fid, Name: name, Mode: mode, Perm: perm} - rx, err := conn.rpc(tx, nil) - if err != nil { - return err - } - fid.mode = mode - fid.qid = rx.Qid - return nil -} - func (fid *Fid) Dirread() ([]*plan9.Dir, error) { buf := make([]byte, plan9.STATMAX) n, err := fid.Read(buf) @@ -134,258 +64,6 @@ func dirUnpack(b []byte) ([]*plan9.Dir, error) { return dirs, err } -func (fid *Fid) Open(mode uint8) error { - conn, err := fid.conn() - if err != nil { - return err - } - tx := &plan9.Fcall{Type: plan9.Topen, Fid: fid.fid, Mode: mode} - if _, err := conn.rpc(tx, nil); err != nil { - return err - } - fid.mode = mode - return nil -} - -func (fid *Fid) Qid() plan9.Qid { - return fid.qid -} - -func (fid *Fid) Read(b []byte) (n int, err error) { - return fid.readAt(b, -1) -} - -func (fid *Fid) ReadAt(b []byte, offset int64) (n int, err error) { - for len(b) > 0 { - m, err := fid.readAt(b, offset) - if err != nil { - return n, err - } - n += m - b = b[m:] - if offset != -1 { - offset += int64(m) - } - } - return n, nil -} - -func (fid *Fid) readAt(b []byte, offset int64) (n int, err error) { - conn, err := fid.conn() - if err != nil { - return 0, err - } - msize := conn.msize - plan9.IOHDRSZ - n = len(b) - if uint32(n) > msize { - n = int(msize) - } - o := offset - if o == -1 { - fid.f.Lock() - o = fid.offset - fid.f.Unlock() - } - tx := &plan9.Fcall{Type: plan9.Tread, Fid: fid.fid, Offset: uint64(o), Count: uint32(n)} - rx, err := conn.rpc(tx, nil) - if err != nil { - return 0, err - } - if len(rx.Data) == 0 { - return 0, io.EOF - } - copy(b, rx.Data) - if offset == -1 { - fid.f.Lock() - fid.offset += int64(len(rx.Data)) - fid.f.Unlock() - } - return len(rx.Data), nil -} - func (fid *Fid) ReadFull(b []byte) (n int, err error) { return io.ReadFull(fid, b) } - -func (fid *Fid) Remove() error { - conn, err := fid.conn() - if err != nil { - return err - } - tx := &plan9.Fcall{Type: plan9.Tremove, Fid: fid.fid} - _, err = conn.rpc(tx, fid) - return err -} - -func (fid *Fid) Seek(n int64, whence int) (int64, error) { - switch whence { - case 0: - fid.f.Lock() - fid.offset = n - fid.f.Unlock() - - case 1: - fid.f.Lock() - n += fid.offset - if n < 0 { - fid.f.Unlock() - return 0, Error("negative offset") - } - fid.offset = n - fid.f.Unlock() - - case 2: - d, err := fid.Stat() - if err != nil { - return 0, err - } - n += int64(d.Length) - if n < 0 { - return 0, Error("negative offset") - } - fid.f.Lock() - fid.offset = n - fid.f.Unlock() - - default: - return 0, Error("bad whence in seek") - } - - return n, nil -} - -func (fid *Fid) Stat() (*plan9.Dir, error) { - conn, err := fid.conn() - if err != nil { - return nil, err - } - tx := &plan9.Fcall{Type: plan9.Tstat, Fid: fid.fid} - rx, err := conn.rpc(tx, nil) - if err != nil { - return nil, err - } - return plan9.UnmarshalDir(rx.Stat) -} - -// TODO(rsc): Could use ...string instead? -func (fid *Fid) Walk(name string) (*Fid, error) { - conn, err := fid.conn() - if err != nil { - return nil, err - } - wfidnum, err := conn.newfidnum() - if err != nil { - return nil, err - } - - // Split, delete empty strings and dot. - elem := strings.Split(name, "/") - j := 0 - for _, e := range elem { - if e != "" && e != "." { - elem[j] = e - j++ - } - } - elem = elem[0:j] - - var wfid *Fid - fromfidnum := fid.fid - for nwalk := 0; ; nwalk++ { - n := len(elem) - if n > plan9.MAXWELEM { - n = plan9.MAXWELEM - } - tx := &plan9.Fcall{Type: plan9.Twalk, Fid: fromfidnum, Newfid: wfidnum, Wname: elem[0:n]} - rx, err := conn.rpc(tx, nil) - if err == nil && len(rx.Wqid) != n { - err = Error("file '" + name + "' not found") - } - if err != nil { - if wfid != nil { - wfid.Close() - } - return nil, err - } - if n == 0 { - wfid = conn.newFid(wfidnum, fid.qid) - } else { - wfid = conn.newFid(wfidnum, rx.Wqid[n-1]) - } - elem = elem[n:] - if len(elem) == 0 { - break - } - fromfidnum = wfid.fid - } - return wfid, nil -} - -func (fid *Fid) Write(b []byte) (n int, err error) { - return fid.WriteAt(b, -1) -} - -func (fid *Fid) WriteAt(b []byte, offset int64) (n int, err error) { - conn, err := fid.conn() - if err != nil { - return 0, err - } - msize := conn.msize - plan9.IOHDRSIZE - tot := 0 - n = len(b) - first := true - for tot < n || first { - want := n - tot - if uint32(want) > msize { - want = int(msize) - } - got, err := fid.writeAt(b[tot:tot+want], offset) - tot += got - if err != nil { - return tot, err - } - if offset != -1 { - offset += int64(got) - } - first = false - } - return tot, nil -} - -func (fid *Fid) writeAt(b []byte, offset int64) (n int, err error) { - conn, err := fid.conn() - if err != nil { - return 0, err - } - o := offset - if o == -1 { - fid.f.Lock() - o = fid.offset - fid.f.Unlock() - } - tx := &plan9.Fcall{Type: plan9.Twrite, Fid: fid.fid, Offset: uint64(o), Data: b} - rx, err := conn.rpc(tx, nil) - if err != nil { - return 0, err - } - if offset == -1 && rx.Count > 0 { - fid.f.Lock() - fid.offset += int64(rx.Count) - fid.f.Unlock() - } - return int(rx.Count), nil -} - -func (fid *Fid) Wstat(d *plan9.Dir) error { - conn, err := fid.conn() - if err != nil { - return err - } - b, err := d.Bytes() - if err != nil { - return err - } - tx := &plan9.Fcall{Type: plan9.Twstat, Fid: fid.fid, Stat: b} - _, err = conn.rpc(tx, nil) - return err -} diff --git a/plan9/client/fid_p9p.go b/plan9/client/fid_p9p.go new file mode 100644 index 0000000..e7a58f9 --- /dev/null +++ b/plan9/client/fid_p9p.go @@ -0,0 +1,332 @@ +//go:build !plan9 +// +build !plan9 + +package client + +import ( + "io" + "os" + "strings" + "sync" + + "9fans.net/go/plan9" +) + +func getuser() string { return os.Getenv("USER") } + +type Fid struct { + qid plan9.Qid + fid uint32 + mode uint8 + // f guards offset and c. + f sync.Mutex + // c holds the underlying connection. + // It's nil after the Fid has been closed. + _c *conn + offset int64 +} + +func (fid *Fid) conn() (*conn, error) { + fid.f.Lock() + c := fid._c + fid.f.Unlock() + if c == nil { + return nil, errClosed + } + return c, nil +} + +func (fid *Fid) Close() error { + if fid == nil { + // TODO why is Close allowed on a nil fid but no other operations? + return nil + } + conn, err := fid.conn() + if err != nil { + return err + } + tx := &plan9.Fcall{Type: plan9.Tclunk, Fid: fid.fid} + _, err = conn.rpc(tx, fid) + return err +} + +// clunked marks the fid as clunked and closes it. This is called +// just before sending a message that will clunk it. +func (fid *Fid) clunked() error { + fid.f.Lock() + defer fid.f.Unlock() + if fid._c == nil { + return errClosed + } + fid._c.putfidnum(fid.fid) + fid._c.release() + fid._c = nil + return nil +} + +func (fid *Fid) Create(name string, mode uint8, perm plan9.Perm) error { + conn, err := fid.conn() + if err != nil { + return err + } + tx := &plan9.Fcall{Type: plan9.Tcreate, Fid: fid.fid, Name: name, Mode: mode, Perm: perm} + rx, err := conn.rpc(tx, nil) + if err != nil { + return err + } + fid.mode = mode + fid.qid = rx.Qid + return nil +} + +func (fid *Fid) Open(mode uint8) error { + conn, err := fid.conn() + if err != nil { + return err + } + tx := &plan9.Fcall{Type: plan9.Topen, Fid: fid.fid, Mode: mode} + if _, err := conn.rpc(tx, nil); err != nil { + return err + } + fid.mode = mode + return nil +} + +func (fid *Fid) Qid() plan9.Qid { + return fid.qid +} + +func (fid *Fid) Read(b []byte) (n int, err error) { + return fid.ReadAt(b, -1) +} + +func (fid *Fid) ReadAt(b []byte, offset int64) (n int, err error) { + for len(b) > 0 { + m, err := fid.readAt(b, offset) + if err != nil { + return n, err + } + n += m + b = b[m:] + if offset != -1 { + offset += int64(m) + } + } + return n, nil +} + +func (fid *Fid) readAt(b []byte, offset int64) (n int, err error) { + conn, err := fid.conn() + if err != nil { + return 0, err + } + msize := conn.msize - plan9.IOHDRSZ + n = len(b) + if uint32(n) > msize { + n = int(msize) + } + o := offset + if o == -1 { + fid.f.Lock() + o = fid.offset + fid.f.Unlock() + } + tx := &plan9.Fcall{Type: plan9.Tread, Fid: fid.fid, Offset: uint64(o), Count: uint32(n)} + rx, err := conn.rpc(tx, nil) + if err != nil { + return 0, err + } + if len(rx.Data) == 0 { + return 0, io.EOF + } + copy(b, rx.Data) + if offset == -1 { + fid.f.Lock() + fid.offset += int64(len(rx.Data)) + fid.f.Unlock() + } + return len(rx.Data), nil +} + +func (fid *Fid) Remove() error { + conn, err := fid.conn() + if err != nil { + return err + } + tx := &plan9.Fcall{Type: plan9.Tremove, Fid: fid.fid} + _, err = conn.rpc(tx, fid) + return err +} + +func (fid *Fid) Seek(n int64, whence int) (int64, error) { + switch whence { + case 0: + fid.f.Lock() + fid.offset = n + fid.f.Unlock() + + case 1: + fid.f.Lock() + n += fid.offset + if n < 0 { + fid.f.Unlock() + return 0, Error("negative offset") + } + fid.offset = n + fid.f.Unlock() + + case 2: + d, err := fid.Stat() + if err != nil { + return 0, err + } + n += int64(d.Length) + if n < 0 { + return 0, Error("negative offset") + } + fid.f.Lock() + fid.offset = n + fid.f.Unlock() + + default: + return 0, Error("bad whence in seek") + } + + return n, nil +} + +func (fid *Fid) Stat() (*plan9.Dir, error) { + conn, err := fid.conn() + if err != nil { + return nil, err + } + tx := &plan9.Fcall{Type: plan9.Tstat, Fid: fid.fid} + rx, err := conn.rpc(tx, nil) + if err != nil { + return nil, err + } + return plan9.UnmarshalDir(rx.Stat) +} + +// TODO(rsc): Could use ...string instead? +func (fid *Fid) Walk(name string) (*Fid, error) { + conn, err := fid.conn() + if err != nil { + return nil, err + } + wfidnum, err := conn.newfidnum() + if err != nil { + return nil, err + } + + // Split, delete empty strings and dot. + elem := strings.Split(name, "/") + j := 0 + for _, e := range elem { + if e != "" && e != "." { + elem[j] = e + j++ + } + } + elem = elem[0:j] + + var wfid *Fid + fromfidnum := fid.fid + for nwalk := 0; ; nwalk++ { + n := len(elem) + if n > plan9.MAXWELEM { + n = plan9.MAXWELEM + } + tx := &plan9.Fcall{Type: plan9.Twalk, Fid: fromfidnum, Newfid: wfidnum, Wname: elem[0:n]} + rx, err := conn.rpc(tx, nil) + if err == nil && len(rx.Wqid) != n { + err = Error("file '" + name + "' not found") + } + if err != nil { + if wfid != nil { + wfid.Close() + } + return nil, err + } + if n == 0 { + wfid = conn.newFid(wfidnum, fid.qid) + } else { + wfid = conn.newFid(wfidnum, rx.Wqid[n-1]) + } + elem = elem[n:] + if len(elem) == 0 { + break + } + fromfidnum = wfid.fid + } + return wfid, nil +} + +func (fid *Fid) Write(b []byte) (n int, err error) { + return fid.WriteAt(b, -1) +} + +func (fid *Fid) WriteAt(b []byte, offset int64) (n int, err error) { + conn, err := fid.conn() + if err != nil { + return 0, err + } + msize := conn.msize - plan9.IOHDRSIZE + tot := 0 + n = len(b) + first := true + for tot < n || first { + want := n - tot + if uint32(want) > msize { + want = int(msize) + } + got, err := fid.writeAt(b[tot:tot+want], offset) + tot += got + if err != nil { + return tot, err + } + if offset != -1 { + offset += int64(got) + } + first = false + } + return tot, nil +} + +func (fid *Fid) writeAt(b []byte, offset int64) (n int, err error) { + conn, err := fid.conn() + if err != nil { + return 0, err + } + o := offset + if o == -1 { + fid.f.Lock() + o = fid.offset + fid.f.Unlock() + } + tx := &plan9.Fcall{Type: plan9.Twrite, Fid: fid.fid, Offset: uint64(o), Data: b} + rx, err := conn.rpc(tx, nil) + if err != nil { + return 0, err + } + if offset == -1 && rx.Count > 0 { + fid.f.Lock() + fid.offset += int64(rx.Count) + fid.f.Unlock() + } + return int(rx.Count), nil +} + +func (fid *Fid) Wstat(d *plan9.Dir) error { + conn, err := fid.conn() + if err != nil { + return err + } + b, err := d.Bytes() + if err != nil { + return err + } + tx := &plan9.Fcall{Type: plan9.Twstat, Fid: fid.fid, Stat: b} + _, err = conn.rpc(tx, nil) + return err +} diff --git a/plan9/client/fid_plan9.go b/plan9/client/fid_plan9.go new file mode 100644 index 0000000..aeb55db --- /dev/null +++ b/plan9/client/fid_plan9.go @@ -0,0 +1,7 @@ +package client + +import "os" + +type Fid struct { + *os.File +} diff --git a/plan9/client/fsys.go b/plan9/client/fsys.go index 86b0e4e..ea4b18a 100644 --- a/plan9/client/fsys.go +++ b/plan9/client/fsys.go @@ -1,3 +1,5 @@ +// +build !plan9 + package client import ( diff --git a/plan9/client/fsys_plan9.go b/plan9/client/fsys_plan9.go new file mode 100644 index 0000000..f72c95d --- /dev/null +++ b/plan9/client/fsys_plan9.go @@ -0,0 +1,41 @@ +package client + +import ( + "os" + "path/filepath" + + "9fans.net/go/plan9" +) + +type Fsys struct { + Mtpt string +} + +func (c *Conn) Attach(afid *Fid, user, aname string) (*Fsys, error) { + panic("unimplemented") +} + +func (fs *Fsys) Access(name string, mode int) error { + panic("unimplemented") +} + +func (fs *Fsys) Create(name string, mode uint8, perm plan9.Perm) (*Fid, error) { + panic("unimplemented") +} + +func (fs *Fsys) Open(name string, mode uint8) (*Fid, error) { + f, err := os.OpenFile(filepath.Join(fs.Mtpt, name), int(mode), 0) + return &Fid{File: f}, err +} + +func (fs *Fsys) Remove(name string) error { + panic("unimplemented") +} + +func (fs *Fsys) Stat(name string) (*plan9.Dir, error) { + panic("unimplemented") +} + +func (fs *Fsys) Wstat(name string, d *plan9.Dir) error { + panic("unimplemented") +} diff --git a/plan9/const.go b/plan9/const.go index 7a52235..28a4092 100644 --- a/plan9/const.go +++ b/plan9/const.go @@ -51,4 +51,8 @@ const ( NOFID = 0xffffffff NOUID = 0xffffffff IOHDRSZ = 24 + + MREPL = 0x0000 + MBEFORE = 0x0001 + MAFTER = 0x0002 ) diff --git a/plumb/plumb.go b/plumb/plumb.go index 8076879..128f93b 100644 --- a/plumb/plumb.go +++ b/plumb/plumb.go @@ -39,10 +39,6 @@ var fsys *client.Fsys var fsysErr error var fsysOnce sync.Once -func mountPlumb() { - fsys, fsysErr = client.MountService("plumb") -} - // Open opens the plumbing file with the given name and open mode. func Open(name string, mode int) (*client.Fid, error) { fsysOnce.Do(mountPlumb) diff --git a/plumb/plumb_p9p.go b/plumb/plumb_p9p.go new file mode 100644 index 0000000..96c2dc3 --- /dev/null +++ b/plumb/plumb_p9p.go @@ -0,0 +1,11 @@ +// +build !plan9 + +package plumb + +import ( + "9fans.net/go/plan9/client" +) + +func mountPlumb() { + fsys, fsysErr = client.MountService("plumb") +} diff --git a/plumb/plumb_plan9.go b/plumb/plumb_plan9.go new file mode 100644 index 0000000..b062c3d --- /dev/null +++ b/plumb/plumb_plan9.go @@ -0,0 +1,10 @@ +package plumb + +import ( + "9fans.net/go/plan9/client" +) + +func mountPlumb() { + fsys = &client.Fsys{Mtpt: "/mnt/plumb"} + fsysErr = nil +}