488 lines
8.3 KiB
Go
488 lines
8.3 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.
|
|
|
|
package go9p
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/user"
|
|
"path"
|
|
"sort"
|
|
"strconv"
|
|
"syscall"
|
|
)
|
|
|
|
type ufsFid struct {
|
|
path string
|
|
file *os.File
|
|
dirs []os.FileInfo
|
|
direntends []int
|
|
dirents []byte
|
|
diroffset uint64
|
|
st os.FileInfo
|
|
}
|
|
|
|
type Ufs struct {
|
|
Srv
|
|
Root string
|
|
}
|
|
|
|
func toError(err error) *Error {
|
|
var ecode uint32
|
|
|
|
ename := err.Error()
|
|
if e, ok := err.(syscall.Errno); ok {
|
|
ecode = uint32(e)
|
|
} else {
|
|
ecode = EIO
|
|
}
|
|
|
|
return &Error{ename, ecode}
|
|
}
|
|
|
|
func (fid *ufsFid) stat() *Error {
|
|
var err error
|
|
|
|
fid.st, err = os.Lstat(fid.path)
|
|
if err != nil {
|
|
return toError(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func omode2uflags(mode uint8) int {
|
|
ret := int(0)
|
|
switch mode & 3 {
|
|
case OREAD:
|
|
ret = os.O_RDONLY
|
|
break
|
|
|
|
case ORDWR:
|
|
ret = os.O_RDWR
|
|
break
|
|
|
|
case OWRITE:
|
|
ret = os.O_WRONLY
|
|
break
|
|
|
|
case OEXEC:
|
|
ret = os.O_RDONLY
|
|
break
|
|
}
|
|
|
|
if mode&OTRUNC != 0 {
|
|
ret |= os.O_TRUNC
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func dir2QidType(d os.FileInfo) uint8 {
|
|
ret := uint8(0)
|
|
if d.IsDir() {
|
|
ret |= QTDIR
|
|
}
|
|
|
|
if d.Mode()&os.ModeSymlink != 0 {
|
|
ret |= QTSYMLINK
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func dir2Npmode(d os.FileInfo, dotu bool) uint32 {
|
|
ret := uint32(d.Mode() & 0777)
|
|
if d.IsDir() {
|
|
ret |= DMDIR
|
|
}
|
|
|
|
if dotu {
|
|
mode := d.Mode()
|
|
if mode&os.ModeSymlink != 0 {
|
|
ret |= DMSYMLINK
|
|
}
|
|
|
|
if mode&os.ModeSocket != 0 {
|
|
ret |= DMSOCKET
|
|
}
|
|
|
|
if mode&os.ModeNamedPipe != 0 {
|
|
ret |= DMNAMEDPIPE
|
|
}
|
|
|
|
if mode&os.ModeDevice != 0 {
|
|
ret |= DMDEVICE
|
|
}
|
|
|
|
if mode&os.ModeSetuid != 0 {
|
|
ret |= DMSETUID
|
|
}
|
|
|
|
if mode&os.ModeSetgid != 0 {
|
|
ret |= DMSETGID
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// Dir is an instantiation of the p.Dir structure
|
|
// that can act as a receiver for local methods.
|
|
type ufsDir struct {
|
|
Dir
|
|
}
|
|
|
|
func (*Ufs) ConnOpened(conn *Conn) {
|
|
if conn.Srv.Debuglevel > 0 {
|
|
log.Println("connected")
|
|
}
|
|
}
|
|
|
|
func (*Ufs) ConnClosed(conn *Conn) {
|
|
if conn.Srv.Debuglevel > 0 {
|
|
log.Println("disconnected")
|
|
}
|
|
}
|
|
|
|
func (*Ufs) FidDestroy(sfid *SrvFid) {
|
|
var fid *ufsFid
|
|
|
|
if sfid.Aux == nil {
|
|
return
|
|
}
|
|
|
|
fid = sfid.Aux.(*ufsFid)
|
|
if fid.file != nil {
|
|
fid.file.Close()
|
|
}
|
|
}
|
|
|
|
func (ufs *Ufs) Attach(req *SrvReq) {
|
|
if req.Afid != nil {
|
|
req.RespondError(Enoauth)
|
|
return
|
|
}
|
|
|
|
tc := req.Tc
|
|
fid := new(ufsFid)
|
|
// You can think of the ufs.Root as a 'chroot' of a sort.
|
|
// clients attach are not allowed to go outside the
|
|
// directory represented by ufs.Root
|
|
fid.path = path.Join(ufs.Root, tc.Aname)
|
|
|
|
req.Fid.Aux = fid
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
qid := dir2Qid(fid.st)
|
|
req.RespondRattach(qid)
|
|
}
|
|
|
|
func (*Ufs) Flush(req *SrvReq) {}
|
|
|
|
func (*Ufs) Walk(req *SrvReq) {
|
|
fid := req.Fid.Aux.(*ufsFid)
|
|
tc := req.Tc
|
|
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
if req.Newfid.Aux == nil {
|
|
req.Newfid.Aux = new(ufsFid)
|
|
}
|
|
|
|
nfid := req.Newfid.Aux.(*ufsFid)
|
|
wqids := make([]Qid, len(tc.Wname))
|
|
path := fid.path
|
|
i := 0
|
|
for ; i < len(tc.Wname); i++ {
|
|
p := path + "/" + tc.Wname[i]
|
|
st, err := os.Lstat(p)
|
|
if err != nil {
|
|
if i == 0 {
|
|
req.RespondError(Enoent)
|
|
return
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
wqids[i] = *dir2Qid(st)
|
|
path = p
|
|
}
|
|
|
|
nfid.path = path
|
|
req.RespondRwalk(wqids[0:i])
|
|
}
|
|
|
|
func (*Ufs) Open(req *SrvReq) {
|
|
fid := req.Fid.Aux.(*ufsFid)
|
|
tc := req.Tc
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
var e error
|
|
fid.file, e = os.OpenFile(fid.path, omode2uflags(tc.Mode), 0)
|
|
if e != nil {
|
|
req.RespondError(toError(e))
|
|
return
|
|
}
|
|
|
|
req.RespondRopen(dir2Qid(fid.st), 0)
|
|
}
|
|
|
|
func (*Ufs) Create(req *SrvReq) {
|
|
fid := req.Fid.Aux.(*ufsFid)
|
|
tc := req.Tc
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
path := fid.path + "/" + tc.Name
|
|
var e error = nil
|
|
var file *os.File = nil
|
|
switch {
|
|
case tc.Perm&DMDIR != 0:
|
|
e = os.Mkdir(path, os.FileMode(tc.Perm&0777))
|
|
|
|
case tc.Perm&DMSYMLINK != 0:
|
|
e = os.Symlink(tc.Ext, path)
|
|
|
|
case tc.Perm&DMLINK != 0:
|
|
n, e := strconv.ParseUint(tc.Ext, 10, 0)
|
|
if e != nil {
|
|
break
|
|
}
|
|
|
|
ofid := req.Conn.FidGet(uint32(n))
|
|
if ofid == nil {
|
|
req.RespondError(Eunknownfid)
|
|
return
|
|
}
|
|
|
|
e = os.Link(ofid.Aux.(*ufsFid).path, path)
|
|
ofid.DecRef()
|
|
|
|
case tc.Perm&DMNAMEDPIPE != 0:
|
|
case tc.Perm&DMDEVICE != 0:
|
|
req.RespondError(&Error{"not implemented", EIO})
|
|
return
|
|
|
|
default:
|
|
var mode uint32 = tc.Perm & 0777
|
|
if req.Conn.Dotu {
|
|
if tc.Perm&DMSETUID > 0 {
|
|
mode |= syscall.S_ISUID
|
|
}
|
|
if tc.Perm&DMSETGID > 0 {
|
|
mode |= syscall.S_ISGID
|
|
}
|
|
}
|
|
file, e = os.OpenFile(path, omode2uflags(tc.Mode)|os.O_CREATE, os.FileMode(mode))
|
|
}
|
|
|
|
if file == nil && e == nil {
|
|
file, e = os.OpenFile(path, omode2uflags(tc.Mode), 0)
|
|
}
|
|
|
|
if e != nil {
|
|
req.RespondError(toError(e))
|
|
return
|
|
}
|
|
|
|
fid.path = path
|
|
fid.file = file
|
|
err = fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
req.RespondRcreate(dir2Qid(fid.st), 0)
|
|
}
|
|
|
|
func (*Ufs) Read(req *SrvReq) {
|
|
fid := req.Fid.Aux.(*ufsFid)
|
|
tc := req.Tc
|
|
rc := req.Rc
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
InitRread(rc, tc.Count)
|
|
var count int
|
|
var e error
|
|
if fid.st.IsDir() {
|
|
if tc.Offset == 0 {
|
|
var e error
|
|
// If we got here, it was open. Can't really seek
|
|
// in most cases, just close and reopen it.
|
|
fid.file.Close()
|
|
if fid.file, e = os.OpenFile(fid.path, omode2uflags(req.Fid.Omode), 0); e != nil {
|
|
req.RespondError(toError(e))
|
|
return
|
|
}
|
|
|
|
if fid.dirs, e = fid.file.Readdir(-1); e != nil {
|
|
req.RespondError(toError(e))
|
|
return
|
|
}
|
|
|
|
fid.dirents = nil
|
|
fid.direntends = nil
|
|
for i := 0; i < len(fid.dirs); i++ {
|
|
path := fid.path + "/" + fid.dirs[i].Name()
|
|
st, _ := dir2Dir(path, fid.dirs[i], req.Conn.Dotu, req.Conn.Srv.Upool)
|
|
if st == nil {
|
|
continue
|
|
}
|
|
b := PackDir(st, req.Conn.Dotu)
|
|
fid.dirents = append(fid.dirents, b...)
|
|
count += len(b)
|
|
fid.direntends = append(fid.direntends, count)
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case tc.Offset > uint64(len(fid.dirents)):
|
|
count = 0
|
|
case len(fid.dirents[tc.Offset:]) > int(tc.Count):
|
|
count = int(tc.Count)
|
|
default:
|
|
count = len(fid.dirents[tc.Offset:])
|
|
}
|
|
|
|
if !*Akaros {
|
|
nextend := sort.SearchInts(fid.direntends, int(tc.Offset)+count)
|
|
if nextend < len(fid.direntends) {
|
|
if fid.direntends[nextend] > int(tc.Offset)+count {
|
|
if nextend > 0 {
|
|
count = fid.direntends[nextend-1] - int(tc.Offset)
|
|
} else {
|
|
count = 0
|
|
}
|
|
}
|
|
}
|
|
if count == 0 && int(tc.Offset) < len(fid.dirents) && len(fid.dirents) > 0 {
|
|
req.RespondError(&Error{"too small read size for dir entry", EINVAL})
|
|
return
|
|
}
|
|
}
|
|
|
|
copy(rc.Data, fid.dirents[tc.Offset:int(tc.Offset)+count])
|
|
|
|
} else {
|
|
count, e = fid.file.ReadAt(rc.Data, int64(tc.Offset))
|
|
if e != nil && e != io.EOF {
|
|
req.RespondError(toError(e))
|
|
return
|
|
}
|
|
|
|
}
|
|
|
|
SetRreadCount(rc, uint32(count))
|
|
req.Respond()
|
|
}
|
|
|
|
func (*Ufs) Write(req *SrvReq) {
|
|
fid := req.Fid.Aux.(*ufsFid)
|
|
tc := req.Tc
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
n, e := fid.file.WriteAt(tc.Data, int64(tc.Offset))
|
|
if e != nil {
|
|
req.RespondError(toError(e))
|
|
return
|
|
}
|
|
|
|
req.RespondRwrite(uint32(n))
|
|
}
|
|
|
|
func (*Ufs) Clunk(req *SrvReq) { req.RespondRclunk() }
|
|
|
|
func (*Ufs) Remove(req *SrvReq) {
|
|
fid := req.Fid.Aux.(*ufsFid)
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
e := os.Remove(fid.path)
|
|
if e != nil {
|
|
req.RespondError(toError(e))
|
|
return
|
|
}
|
|
|
|
req.RespondRremove()
|
|
}
|
|
|
|
func (*Ufs) Stat(req *SrvReq) {
|
|
fid := req.Fid.Aux.(*ufsFid)
|
|
err := fid.stat()
|
|
if err != nil {
|
|
req.RespondError(err)
|
|
return
|
|
}
|
|
|
|
st, derr := dir2Dir(fid.path, fid.st, req.Conn.Dotu, req.Conn.Srv.Upool)
|
|
if st == nil {
|
|
req.RespondError(derr)
|
|
return
|
|
}
|
|
|
|
req.RespondRstat(st)
|
|
}
|
|
|
|
func lookup(uid string, group bool) (uint32, *Error) {
|
|
if uid == "" {
|
|
return NOUID, nil
|
|
}
|
|
usr, e := user.Lookup(uid)
|
|
if e != nil {
|
|
return NOUID, toError(e)
|
|
}
|
|
conv := usr.Uid
|
|
if group {
|
|
conv = usr.Gid
|
|
}
|
|
u, e := strconv.Atoi(conv)
|
|
if e != nil {
|
|
return NOUID, toError(e)
|
|
}
|
|
return uint32(u), nil
|
|
}
|
|
|
|
/* enables "Akaros" capabilities, which right now means
|
|
* a sane error message format.
|
|
*/
|
|
|
|
// (r2d4): We don't want this exposed in minikube right now
|
|
// var Akaros = flag.Bool("akaros", false, "Akaros extensions")
|
|
var Akaros = boolPointer(false)
|
|
|
|
func boolPointer(b bool) *bool {
|
|
return &b
|
|
}
|