531 lines
14 KiB
Go
531 lines
14 KiB
Go
// Copyright 2009 The Go9p Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// The p9 package go9provides the definitions and functions used to implement
|
|
// the 9P2000 protocol.
|
|
// TODO.
|
|
// All the packet conversion code in this file is crap and needs a rewrite.
|
|
package go9p
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// 9P2000 message types
|
|
const (
|
|
Tversion = 100 + iota
|
|
Rversion
|
|
Tauth
|
|
Rauth
|
|
Tattach
|
|
Rattach
|
|
Terror
|
|
Rerror
|
|
Tflush
|
|
Rflush
|
|
Twalk
|
|
Rwalk
|
|
Topen
|
|
Ropen
|
|
Tcreate
|
|
Rcreate
|
|
Tread
|
|
Rread
|
|
Twrite
|
|
Rwrite
|
|
Tclunk
|
|
Rclunk
|
|
Tremove
|
|
Rremove
|
|
Tstat
|
|
Rstat
|
|
Twstat
|
|
Rwstat
|
|
Tlast
|
|
)
|
|
|
|
const (
|
|
MSIZE = 1048576 + IOHDRSZ // default message size (1048576+IOHdrSz)
|
|
IOHDRSZ = 24 // the non-data size of the Twrite messages
|
|
PORT = 564 // default port for 9P file servers
|
|
)
|
|
|
|
// Qid types
|
|
const (
|
|
QTDIR = 0x80 // directories
|
|
QTAPPEND = 0x40 // append only files
|
|
QTEXCL = 0x20 // exclusive use files
|
|
QTMOUNT = 0x10 // mounted channel
|
|
QTAUTH = 0x08 // authentication file
|
|
QTTMP = 0x04 // non-backed-up file
|
|
QTSYMLINK = 0x02 // symbolic link (Unix, 9P2000.u)
|
|
QTLINK = 0x01 // hard link (Unix, 9P2000.u)
|
|
QTFILE = 0x00
|
|
)
|
|
|
|
// Flags for the mode field in Topen and Tcreate messages
|
|
const (
|
|
OREAD = 0 // open read-only
|
|
OWRITE = 1 // open write-only
|
|
ORDWR = 2 // open read-write
|
|
OEXEC = 3 // execute (== read but check execute permission)
|
|
OTRUNC = 16 // or'ed in (except for exec), truncate file first
|
|
OCEXEC = 32 // or'ed in, close on exec
|
|
ORCLOSE = 64 // or'ed in, remove on close
|
|
)
|
|
|
|
// File modes
|
|
const (
|
|
DMDIR = 0x80000000 // mode bit for directories
|
|
DMAPPEND = 0x40000000 // mode bit for append only files
|
|
DMEXCL = 0x20000000 // mode bit for exclusive use files
|
|
DMMOUNT = 0x10000000 // mode bit for mounted channel
|
|
DMAUTH = 0x08000000 // mode bit for authentication file
|
|
DMTMP = 0x04000000 // mode bit for non-backed-up file
|
|
DMSYMLINK = 0x02000000 // mode bit for symbolic link (Unix, 9P2000.u)
|
|
DMLINK = 0x01000000 // mode bit for hard link (Unix, 9P2000.u)
|
|
DMDEVICE = 0x00800000 // mode bit for device file (Unix, 9P2000.u)
|
|
DMNAMEDPIPE = 0x00200000 // mode bit for named pipe (Unix, 9P2000.u)
|
|
DMSOCKET = 0x00100000 // mode bit for socket (Unix, 9P2000.u)
|
|
DMSETUID = 0x00080000 // mode bit for setuid (Unix, 9P2000.u)
|
|
DMSETGID = 0x00040000 // mode bit for setgid (Unix, 9P2000.u)
|
|
DMREAD = 0x4 // mode bit for read permission
|
|
DMWRITE = 0x2 // mode bit for write permission
|
|
DMEXEC = 0x1 // mode bit for execute permission
|
|
)
|
|
|
|
const (
|
|
NOTAG uint16 = 0xFFFF // no tag specified
|
|
NOFID uint32 = 0xFFFFFFFF // no fid specified
|
|
NOUID uint32 = 0xFFFFFFFF // no uid specified
|
|
)
|
|
|
|
// Error values
|
|
const (
|
|
EPERM = 1
|
|
ENOENT = 2
|
|
EIO = 5
|
|
EEXIST = 17
|
|
ENOTDIR = 20
|
|
EINVAL = 22
|
|
)
|
|
|
|
// Error represents a 9P2000 (and 9P2000.u) error
|
|
type Error struct {
|
|
Err string // textual representation of the error
|
|
Errornum uint32 // numeric representation of the error (9P2000.u)
|
|
}
|
|
|
|
// File identifier
|
|
type Qid struct {
|
|
Type uint8 // type of the file (high 8 bits of the mode)
|
|
Version uint32 // version number for the path
|
|
Path uint64 // server's unique identification of the file
|
|
}
|
|
|
|
// Dir describes a file
|
|
type Dir struct {
|
|
Size uint16 // size-2 of the Dir on the wire
|
|
Type uint16
|
|
Dev uint32
|
|
Qid // file's Qid
|
|
Mode uint32 // permissions and flags
|
|
Atime uint32 // last access time in seconds
|
|
Mtime uint32 // last modified time in seconds
|
|
Length uint64 // file length in bytes
|
|
Name string // file name
|
|
Uid string // owner name
|
|
Gid string // group name
|
|
Muid string // name of the last user that modified the file
|
|
|
|
/* 9P2000.u extension */
|
|
Ext string // special file's descriptor
|
|
Uidnum uint32 // owner ID
|
|
Gidnum uint32 // group ID
|
|
Muidnum uint32 // ID of the last user that modified the file
|
|
}
|
|
|
|
// Fcall represents a 9P2000 message
|
|
type Fcall struct {
|
|
Size uint32 // size of the message
|
|
Type uint8 // message type
|
|
Fid uint32 // file identifier
|
|
Tag uint16 // message tag
|
|
Msize uint32 // maximum message size (used by Tversion, Rversion)
|
|
Version string // protocol version (used by Tversion, Rversion)
|
|
Oldtag uint16 // tag of the message to flush (used by Tflush)
|
|
Error string // error (used by Rerror)
|
|
Qid // file Qid (used by Rauth, Rattach, Ropen, Rcreate)
|
|
Iounit uint32 // maximum bytes read without breaking in multiple messages (used by Ropen, Rcreate)
|
|
Afid uint32 // authentication fid (used by Tauth, Tattach)
|
|
Uname string // user name (used by Tauth, Tattach)
|
|
Aname string // attach name (used by Tauth, Tattach)
|
|
Perm uint32 // file permission (mode) (used by Tcreate)
|
|
Name string // file name (used by Tcreate)
|
|
Mode uint8 // open mode (used by Topen, Tcreate)
|
|
Newfid uint32 // the fid that represents the file walked to (used by Twalk)
|
|
Wname []string // list of names to walk (used by Twalk)
|
|
Wqid []Qid // list of Qids for the walked files (used by Rwalk)
|
|
Offset uint64 // offset in the file to read/write from/to (used by Tread, Twrite)
|
|
Count uint32 // number of bytes read/written (used by Tread, Rread, Twrite, Rwrite)
|
|
Data []uint8 // data read/to-write (used by Rread, Twrite)
|
|
Dir // file description (used by Rstat, Twstat)
|
|
|
|
/* 9P2000.u extensions */
|
|
Errornum uint32 // error code, 9P2000.u only (used by Rerror)
|
|
Ext string // special file description, 9P2000.u only (used by Tcreate)
|
|
Unamenum uint32 // user ID, 9P2000.u only (used by Tauth, Tattach)
|
|
|
|
Pkt []uint8 // raw packet data
|
|
Buf []uint8 // buffer to put the raw data in
|
|
}
|
|
|
|
// Interface for accessing users and groups
|
|
type Users interface {
|
|
Uid2User(uid int) User
|
|
Uname2User(uname string) User
|
|
Gid2Group(gid int) Group
|
|
Gname2Group(gname string) Group
|
|
}
|
|
|
|
// Represents a user
|
|
type User interface {
|
|
Name() string // user name
|
|
Id() int // user id
|
|
Groups() []Group // groups the user belongs to (can return nil)
|
|
IsMember(g Group) bool // returns true if the user is member of the specified group
|
|
}
|
|
|
|
// Represents a group of users
|
|
type Group interface {
|
|
Name() string // group name
|
|
Id() int // group id
|
|
Members() []User // list of members that belong to the group (can return nil)
|
|
}
|
|
|
|
// minimum size of a 9P2000 message for a type
|
|
var minFcsize = [...]uint32{
|
|
6, /* Tversion msize[4] version[s] */
|
|
6, /* Rversion msize[4] version[s] */
|
|
8, /* Tauth fid[4] uname[s] aname[s] */
|
|
13, /* Rauth aqid[13] */
|
|
12, /* Tattach fid[4] afid[4] uname[s] aname[s] */
|
|
13, /* Rattach qid[13] */
|
|
0, /* Terror */
|
|
2, /* Rerror ename[s] (ecode[4]) */
|
|
2, /* Tflush oldtag[2] */
|
|
0, /* Rflush */
|
|
10, /* Twalk fid[4] newfid[4] nwname[2] */
|
|
2, /* Rwalk nwqid[2] */
|
|
5, /* Topen fid[4] mode[1] */
|
|
17, /* Ropen qid[13] iounit[4] */
|
|
11, /* Tcreate fid[4] name[s] perm[4] mode[1] */
|
|
17, /* Rcreate qid[13] iounit[4] */
|
|
16, /* Tread fid[4] offset[8] count[4] */
|
|
4, /* Rread count[4] */
|
|
16, /* Twrite fid[4] offset[8] count[4] */
|
|
4, /* Rwrite count[4] */
|
|
4, /* Tclunk fid[4] */
|
|
0, /* Rclunk */
|
|
4, /* Tremove fid[4] */
|
|
0, /* Rremove */
|
|
4, /* Tstat fid[4] */
|
|
4, /* Rstat stat[n] */
|
|
8, /* Twstat fid[4] stat[n] */
|
|
0, /* Rwstat */
|
|
20, /* Tbread fileid[8] offset[8] count[4] */
|
|
4, /* Rbread count[4] */
|
|
20, /* Tbwrite fileid[8] offset[8] count[4] */
|
|
4, /* Rbwrite count[4] */
|
|
16, /* Tbtrunc fileid[8] offset[8] */
|
|
0, /* Rbtrunc */
|
|
}
|
|
|
|
// minimum size of a 9P2000.u message for a type
|
|
var minFcusize = [...]uint32{
|
|
6, /* Tversion msize[4] version[s] */
|
|
6, /* Rversion msize[4] version[s] */
|
|
12, /* Tauth fid[4] uname[s] aname[s] */
|
|
13, /* Rauth aqid[13] */
|
|
16, /* Tattach fid[4] afid[4] uname[s] aname[s] */
|
|
13, /* Rattach qid[13] */
|
|
0, /* Terror */
|
|
6, /* Rerror ename[s] (ecode[4]) */
|
|
2, /* Tflush oldtag[2] */
|
|
0, /* Rflush */
|
|
10, /* Twalk fid[4] newfid[4] nwname[2] */
|
|
2, /* Rwalk nwqid[2] */
|
|
5, /* Topen fid[4] mode[1] */
|
|
17, /* Ropen qid[13] iounit[4] */
|
|
13, /* Tcreate fid[4] name[s] perm[4] mode[1] */
|
|
17, /* Rcreate qid[13] iounit[4] */
|
|
16, /* Tread fid[4] offset[8] count[4] */
|
|
4, /* Rread count[4] */
|
|
16, /* Twrite fid[4] offset[8] count[4] */
|
|
4, /* Rwrite count[4] */
|
|
4, /* Tclunk fid[4] */
|
|
0, /* Rclunk */
|
|
4, /* Tremove fid[4] */
|
|
0, /* Rremove */
|
|
4, /* Tstat fid[4] */
|
|
4, /* Rstat stat[n] */
|
|
8, /* Twstat fid[4] stat[n] */
|
|
20, /* Tbread fileid[8] offset[8] count[4] */
|
|
4, /* Rbread count[4] */
|
|
20, /* Tbwrite fileid[8] offset[8] count[4] */
|
|
4, /* Rbwrite count[4] */
|
|
16, /* Tbtrunc fileid[8] offset[8] */
|
|
0, /* Rbtrunc */
|
|
}
|
|
|
|
func gint8(buf []byte) (uint8, []byte) { return buf[0], buf[1:] }
|
|
|
|
func gint16(buf []byte) (uint16, []byte) {
|
|
return uint16(buf[0]) | (uint16(buf[1]) << 8), buf[2:]
|
|
}
|
|
|
|
func gint32(buf []byte) (uint32, []byte) {
|
|
return uint32(buf[0]) | (uint32(buf[1]) << 8) | (uint32(buf[2]) << 16) |
|
|
(uint32(buf[3]) << 24),
|
|
buf[4:]
|
|
}
|
|
|
|
func Gint32(buf []byte) (uint32, []byte) { return gint32(buf) }
|
|
|
|
func gint64(buf []byte) (uint64, []byte) {
|
|
return uint64(buf[0]) | (uint64(buf[1]) << 8) | (uint64(buf[2]) << 16) |
|
|
(uint64(buf[3]) << 24) | (uint64(buf[4]) << 32) | (uint64(buf[5]) << 40) |
|
|
(uint64(buf[6]) << 48) | (uint64(buf[7]) << 56),
|
|
buf[8:]
|
|
}
|
|
|
|
func gstr(buf []byte) (string, []byte) {
|
|
var n uint16
|
|
|
|
if buf == nil || len(buf) < 2 {
|
|
return "", nil
|
|
}
|
|
|
|
n, buf = gint16(buf)
|
|
|
|
if int(n) > len(buf) {
|
|
return "", nil
|
|
}
|
|
|
|
return string(buf[0:n]), buf[n:]
|
|
}
|
|
|
|
func gqid(buf []byte, qid *Qid) []byte {
|
|
qid.Type, buf = gint8(buf)
|
|
qid.Version, buf = gint32(buf)
|
|
qid.Path, buf = gint64(buf)
|
|
|
|
return buf
|
|
}
|
|
|
|
func gstat(buf []byte, d *Dir, dotu bool) ([]byte, error) {
|
|
sz := len(buf)
|
|
d.Size, buf = gint16(buf)
|
|
d.Type, buf = gint16(buf)
|
|
d.Dev, buf = gint32(buf)
|
|
buf = gqid(buf, &d.Qid)
|
|
d.Mode, buf = gint32(buf)
|
|
d.Atime, buf = gint32(buf)
|
|
d.Mtime, buf = gint32(buf)
|
|
d.Length, buf = gint64(buf)
|
|
d.Name, buf = gstr(buf)
|
|
if buf == nil {
|
|
s := fmt.Sprintf("Buffer too short for basic 9p: need %d, have %d",
|
|
49, sz)
|
|
return nil, &Error{s, EINVAL}
|
|
}
|
|
|
|
d.Uid, buf = gstr(buf)
|
|
if buf == nil {
|
|
return nil, &Error{"d.Uid failed", EINVAL}
|
|
}
|
|
d.Gid, buf = gstr(buf)
|
|
if buf == nil {
|
|
return nil, &Error{"d.Gid failed", EINVAL}
|
|
}
|
|
|
|
d.Muid, buf = gstr(buf)
|
|
if buf == nil {
|
|
return nil, &Error{"d.Muid failed", EINVAL}
|
|
}
|
|
|
|
if dotu {
|
|
d.Ext, buf = gstr(buf)
|
|
if buf == nil {
|
|
return nil, &Error{"d.Ext failed", EINVAL}
|
|
}
|
|
|
|
d.Uidnum, buf = gint32(buf)
|
|
d.Gidnum, buf = gint32(buf)
|
|
d.Muidnum, buf = gint32(buf)
|
|
} else {
|
|
d.Uidnum = NOUID
|
|
d.Gidnum = NOUID
|
|
d.Muidnum = NOUID
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
func pint8(val uint8, buf []byte) []byte {
|
|
buf[0] = val
|
|
return buf[1:]
|
|
}
|
|
|
|
func pint16(val uint16, buf []byte) []byte {
|
|
buf[0] = uint8(val)
|
|
buf[1] = uint8(val >> 8)
|
|
return buf[2:]
|
|
}
|
|
|
|
func pint32(val uint32, buf []byte) []byte {
|
|
buf[0] = uint8(val)
|
|
buf[1] = uint8(val >> 8)
|
|
buf[2] = uint8(val >> 16)
|
|
buf[3] = uint8(val >> 24)
|
|
return buf[4:]
|
|
}
|
|
|
|
func pint64(val uint64, buf []byte) []byte {
|
|
buf[0] = uint8(val)
|
|
buf[1] = uint8(val >> 8)
|
|
buf[2] = uint8(val >> 16)
|
|
buf[3] = uint8(val >> 24)
|
|
buf[4] = uint8(val >> 32)
|
|
buf[5] = uint8(val >> 40)
|
|
buf[6] = uint8(val >> 48)
|
|
buf[7] = uint8(val >> 56)
|
|
return buf[8:]
|
|
}
|
|
|
|
func pstr(val string, buf []byte) []byte {
|
|
n := uint16(len(val))
|
|
buf = pint16(n, buf)
|
|
b := []byte(val)
|
|
copy(buf, b)
|
|
return buf[n:]
|
|
}
|
|
|
|
func pqid(val *Qid, buf []byte) []byte {
|
|
buf = pint8(val.Type, buf)
|
|
buf = pint32(val.Version, buf)
|
|
buf = pint64(val.Path, buf)
|
|
|
|
return buf
|
|
}
|
|
|
|
func statsz(d *Dir, dotu bool) int {
|
|
sz := 2 + 2 + 4 + 13 + 4 + 4 + 4 + 8 + 2 + 2 + 2 + 2 + len(d.Name) + len(d.Uid) + len(d.Gid) + len(d.Muid)
|
|
if dotu {
|
|
sz += 2 + 4 + 4 + 4 + len(d.Ext)
|
|
}
|
|
|
|
return sz
|
|
}
|
|
|
|
func pstat(d *Dir, buf []byte, dotu bool) []byte {
|
|
sz := statsz(d, dotu)
|
|
buf = pint16(uint16(sz-2), buf)
|
|
buf = pint16(d.Type, buf)
|
|
buf = pint32(d.Dev, buf)
|
|
buf = pqid(&d.Qid, buf)
|
|
buf = pint32(d.Mode, buf)
|
|
buf = pint32(d.Atime, buf)
|
|
buf = pint32(d.Mtime, buf)
|
|
buf = pint64(d.Length, buf)
|
|
buf = pstr(d.Name, buf)
|
|
buf = pstr(d.Uid, buf)
|
|
buf = pstr(d.Gid, buf)
|
|
buf = pstr(d.Muid, buf)
|
|
if dotu {
|
|
buf = pstr(d.Ext, buf)
|
|
buf = pint32(d.Uidnum, buf)
|
|
buf = pint32(d.Gidnum, buf)
|
|
buf = pint32(d.Muidnum, buf)
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
// Converts a Dir value to its on-the-wire representation and writes it to
|
|
// the buf. Returns the number of bytes written, 0 if there is not enough space.
|
|
func PackDir(d *Dir, dotu bool) []byte {
|
|
sz := statsz(d, dotu)
|
|
buf := make([]byte, sz)
|
|
pstat(d, buf, dotu)
|
|
return buf
|
|
}
|
|
|
|
// Converts the on-the-wire representation of a stat to Stat value.
|
|
// Returns an error if the conversion is impossible, otherwise
|
|
// a pointer to a Stat value.
|
|
func UnpackDir(buf []byte, dotu bool) (d *Dir, b []byte, amt int, err error) {
|
|
sz := 2 + 2 + 4 + 13 + 4 + /* size[2] type[2] dev[4] qid[13] mode[4] */
|
|
4 + 4 + 8 + /* atime[4] mtime[4] length[8] */
|
|
2 + 2 + 2 + 2 /* name[s] uid[s] gid[s] muid[s] */
|
|
|
|
if dotu {
|
|
sz += 2 + 4 + 4 + 4 /* extension[s] n_uid[4] n_gid[4] n_muid[4] */
|
|
}
|
|
|
|
if len(buf) < sz {
|
|
s := fmt.Sprintf("short buffer: Need %d and have %v", sz, len(buf))
|
|
return nil, nil, 0, &Error{s, EINVAL}
|
|
}
|
|
|
|
d = new(Dir)
|
|
b, err = gstat(buf, d, dotu)
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
return d, b, len(buf) - len(b), nil
|
|
|
|
}
|
|
|
|
// Allocates a new Fcall.
|
|
func NewFcall(sz uint32) *Fcall {
|
|
fc := new(Fcall)
|
|
fc.Buf = make([]byte, sz)
|
|
|
|
return fc
|
|
}
|
|
|
|
// Sets the tag of a Fcall.
|
|
func SetTag(fc *Fcall, tag uint16) {
|
|
fc.Tag = tag
|
|
pint16(tag, fc.Pkt[5:])
|
|
}
|
|
|
|
func packCommon(fc *Fcall, size int, id uint8) ([]byte, error) {
|
|
size += 4 + 1 + 2 /* size[4] id[1] tag[2] */
|
|
if len(fc.Buf) < int(size) {
|
|
return nil, &Error{"buffer too small", EINVAL}
|
|
}
|
|
|
|
fc.Size = uint32(size)
|
|
fc.Type = id
|
|
fc.Tag = NOTAG
|
|
p := fc.Buf
|
|
p = pint32(uint32(size), p)
|
|
p = pint8(id, p)
|
|
p = pint16(NOTAG, p)
|
|
fc.Pkt = fc.Buf[0:size]
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func (err *Error) Error() string {
|
|
if err != nil {
|
|
return err.Err
|
|
}
|
|
|
|
return ""
|
|
}
|