# Copyright (c) 2008-2011 Tim Newsham, Andrey Mirtchovski
# Copyright (c) 2011-2012 Peter V. Saveliev
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
9P protocol implementation as documented in plan9 intro(5) and <fcall.h>.
"""

import os
import stat
import sys
import socket
import select
import traceback
import io
import threading
import struct
from . import utils as c9

if sys.version_info[0] == 3:
    unicode = str


IOHDRSZ = 24
PORT = 564

cmdName = {}


Tversion = 100
Rversion = 101
Tauth = 102
Rauth = 103
Tattach = 104
Rattach = 105
Terror = 106
Rerror = 107
Tflush = 108
Rflush = 109
Twalk = 110
Rwalk = 111
Topen = 112
Ropen = 113
Tcreate = 114
Rcreate = 115
Tread = 116
Rread = 117
Twrite = 118
Rwrite = 119
Tclunk = 120
Rclunk = 121
Tremove = 122
Rremove = 123
Tstat = 124
Rstat = 125
Twstat = 126
Rwstat = 127

for i, k in dict(globals()).items():
    try:
        if (i[0] in ('T', 'R')) and isinstance(k, int):
            cmdName[k] = i
    except:
        pass

version = b'9P2000'
versionu = b'9P2000.u'

Ebadoffset = "bad offset"
Ebotch = "9P protocol botch"
Ecreatenondir = "create in non-directory"
Edupfid = "duplicate fid"
Eduptag = "duplicate tag"
Eisdir = "is a directory"
Enocreate = "create prohibited"
Enoremove = "remove prohibited"
Enostat = "stat prohibited"
Enotfound = "file not found"
Enowstat = "wstat prohibited"
Eperm = "permission denied"
Eunknownfid = "unknown fid"
Ebaddir = "bad directory in wstat"
Ewalknotdir = "walk in non-directory"
Eopen = "file not open"

NOTAG = 0xffff
NOFID = 0xffffffff

# for completeness including all of p9p's defines
OREAD = 0         # open for read
OWRITE = 1        # write
ORDWR = 2         # read and 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
ODIRECT = 128     # or'ed in, direct access
ONONBLOCK = 256   # or'ed in, non-blocking call
OEXCL = 0x1000    # or'ed in, exclusive use (create only)
OLOCK = 0x2000    # or'ed in, lock after opening
OAPPEND = 0x4000  # or'ed in, append only

AEXIST = 0        # accessible: exists
AEXEC = 1         # execute access
AWRITE = 2        # write access
AREAD = 4         # read access

# Qid.type
QTDIR = 0x80      # type bit for directories
QTAPPEND = 0x40   # type bit for append only files
QTEXCL = 0x20     # type bit for exclusive use files
QTMOUNT = 0x10    # type bit for mounted channel
QTAUTH = 0x08     # type bit for authentication file
QTTMP = 0x04      # type bit for non-backed-up file
QTSYMLINK = 0x02  # type bit for symbolic link
QTFILE = 0x00     # type bits for plain file

# Dir.mode
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)
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)
DMSTICKY = 0x00010000     # mode bit for sticky bit (Unix, 9P2000.u)

DMREAD = 0x4     # mode bit for read permission
DMWRITE = 0x2    # mode bit for write permission
DMEXEC = 0x1     # mode bit for execute permission

ERRUNDEF = 0xFFFFFFFF
UIDUNDEF = 0xFFFFFFFF

# supported authentication protocols
auths = ['pki', 'sk1']


class Error(Exception):
    pass


class EofError(Error):
    pass


class EdupfidError(Error):
    pass


class RpcError(Error):
    pass


class ServerError(Error):
    pass


class ClientError(Error):
    pass


class VersionError(Error):
    pass


class Marshal9P(object):
    chatty = False

    @property
    def length(self):
        p = self.buf.tell()
        self.buf.seek(0, 2)
        l = self.buf.tell()
        self.buf.seek(p)
        return l

    def enc1(self, x):
        """Encode 1-byte unsigned"""
        self.buf.write(struct.pack('B', x))

    def dec1(self):
        """Decode 1-byte unsigned"""
        return struct.unpack('b', self.buf.read(1))[0]

    def enc2(self, x):
        """Encode 2-byte unsigned"""
        self.buf.write(struct.pack('H', x))

    def dec2(self):
        """Decode 2-byte unsigned"""
        return struct.unpack('H', self.buf.read(2))[0]

    def enc4(self, x):
        """Encode 4-byte unsigned"""
        self.buf.write(struct.pack('I', x))

    def dec4(self):
        """Decode 4-byte unsigned"""
        return struct.unpack('I', self.buf.read(4))[0]

    def enc8(self, x):
        """Encode 8-byte unsigned"""
        self.buf.write(struct.pack('Q', x))

    def dec8(self):
        """Decode 8-byte unsigned"""
        return struct.unpack('Q', self.buf.read(8))[0]

    def encS(self, x):
        """Encode data string with 2-byte length"""
        self.buf.write(struct.pack("H", len(x)))
        if isinstance(x, str) or isinstance(x, unicode):
            x = c9.bytes3(x)
        self.buf.write(x)

    def decS(self):
        """Decode data string with 2-byte length"""
        return self.buf.read(self.dec2())

    def encD(self, d):
        """Encode data string with 4-byte length"""
        self.buf.write(struct.pack("I", len(d)))
        if isinstance(d, str) or isinstance(d, unicode):
            d = c9.bytes3(d)
        self.buf.write(d)

    def decD(self):
        """Decode data string with 4-byte length"""
        return self.buf.read(self.dec4())

    def encF(self, *argv):
        """Encode data directly by struct.pack"""
        self.buf.write(struct.pack(*argv))

    def decF(self, fmt, length):
        """Decode data by struct.unpack"""
        return struct.unpack(fmt, self.buf.read(length))

    def encQ(self, q):
        """Encode Qid structure"""
        self.encF("=BIQ", q.type, q.vers, q.path)

    def decQ(self):
        """Decode Qid structure"""
        return Qid(self.dec1(), self.dec4(), self.dec8())

    def __init__(self, dotu=0, chatty=False):
        self.chatty = chatty
        self.dotu = dotu
        self._lock = threading.Lock()
        self.buf = None

    def _checkType(self, t):
        if t not in cmdName:
            raise Error("Invalid message type %d" % t)

    def _checkSize(self, v, mask):
        if v != v & mask:
            raise Error("Invalid value %d" % v)

    def _checkLen(self, x, l):
        if len(x) != l:
            raise Error("Wrong length %d, expected %d: %r" % (
                len(x), l, x))

    def setBuffer(self, init=b""):
        self.buf = io.BytesIO()
        self.buf.write(init)

    def send(self, fd, fcall):
        "Format and send a message"
        with self._lock:
            self.setBuffer(b"0000")
            self._checkType(fcall.type)
            if self.chatty:
                print("-%d-> %s %s %s" % (fd.fileno(), cmdName[fcall.type], \
                    fcall.tag, fcall.tostr()))
            self.enc(fcall)
            self.buf.seek(0)
            self.enc4(self.length)
            fd.write(self.buf.getvalue())

    def recv(self, fd):
        "Read and decode a message"
        with self._lock:
            size = struct.unpack("I", fd.read(4))[0]
            if size > 0xffffffff or size < 7:
                raise Error("Bad message size: %d" % size)
            self.setBuffer(fd.read(size - 4))
            self.buf.seek(0)
            mtype, tag = self.decF("=BH", 3)
            self._checkType(mtype)
            fcall = Fcall(mtype, tag)
            self.dec(fcall)
            # self._checkResid() -- FIXME: check the message residue
            if self.chatty:
                print("<-%d- %s %s %s" % (fd.fileno(), cmdName[mtype],
                        tag, fcall.tostr()))
            return fcall

    def encstat(self, stats, enclen=1):
        statsz = 0
        for x in stats:
            if self.dotu:
                x.statsz = 61 + \
                        len(x.name) + len(x.uid) + len(x.gid) + \
                        len(x.muid) + len(x.extension)
                statsz += x.statsz
            else:
                x.statsz = 47 + \
                        len(x.name) + len(x.uid) + len(x.gid) + \
                        len(x.muid)
                statsz += x.statsz
        if enclen:
            self.enc2(statsz + 2)

        for x in stats:
            self.encF("=HHIBIQIIIQ",
                    x.statsz, x.type, x.dev, x.qid.type, x.qid.vers,
                    x.qid.path, x.mode, x.atime, x.mtime, x.length)
            self.encS(x.name)
            self.encS(x.uid)
            self.encS(x.gid)
            self.encS(x.muid)
            if self.dotu:
                self.encS(x.extension)
                self.encF("=III",
                        x.uidnum, x.gidnum, x.muidnum)

    def enc(self, fcall):
        self.encF("=BH", fcall.type, fcall.tag)
        if fcall.type in (Tversion, Rversion):
            self.encF("I", fcall.msize)
            self.encS(fcall.version)
        elif fcall.type == Tauth:
            self.encF("I", fcall.afid)
            self.encS(fcall.uname)
            self.encS(fcall.aname)
            if self.dotu:
                self.encF("I", fcall.uidnum)
        elif fcall.type == Rauth:
            self.encQ(fcall.aqid)
        elif fcall.type == Rerror:
            self.encS(fcall.ename)
            if self.dotu:
                self.encF("I", fcall.errno)
        elif fcall.type == Tflush:
            self.encF("H", fcall.oldtag)
        elif fcall.type == Tattach:
            self.encF("=II", fcall.fid, fcall.afid)
            self.encS(fcall.uname)
            self.encS(fcall.aname)
            if self.dotu:
                self.encF("I", fcall.uidnum)
        elif fcall.type == Rattach:
            self.encQ(fcall.qid)
        elif fcall.type == Twalk:
            self.encF("=IIH", fcall.fid, fcall.newfid,
                    len(fcall.wname))
            for x in fcall.wname:
                self.encS(x)
        elif fcall.type == Rwalk:
            self.encF("H", len(fcall.wqid))
            for x in fcall.wqid:
                self.encQ(x)
        elif fcall.type == Topen:
            self.encF("=IB", fcall.fid, fcall.mode)
        elif fcall.type in (Ropen, Rcreate):
            self.encQ(fcall.qid)
            self.encF("I", fcall.iounit)
        elif fcall.type == Tcreate:
            self.encF("I", fcall.fid)
            self.encS(fcall.name)
            self.encF("=IB", fcall.perm, fcall.mode)
            if self.dotu:
                self.encS(fcall.extension)
        elif fcall.type == Tread:
            self.encF("=IQI", fcall.fid, fcall.offset,
                    fcall.count)
        elif fcall.type == Rread:
            self.encD(fcall.data)
        elif fcall.type == Twrite:
            self.encF("=IQI", fcall.fid, fcall.offset,
                    len(fcall.data))
            self.buf.write(fcall.data)
        elif fcall.type == Rwrite:
            self.encF("I", fcall.count)
        elif fcall.type in (Tclunk,  Tremove, Tstat):
            self.encF("I", fcall.fid)
        elif fcall.type in (Rstat, Twstat):
            if fcall.type == Twstat:
                self.encF("I", fcall.fid)
            self.encstat(fcall.stat, 1)

    def decstat(self, stats, enclen=0):
        if enclen:
            # feed 2 bytes of total size
            self.buf.read(2)
        while self.buf.tell() < self.length:
            self.buf.read(2)

            s = Dir(self.dotu)
            (s.type,
                    s.dev,
                    typ, vers, path,
                    s.mode,
                    s.atime,
                    s.mtime,
                    s.length) = self.decF("=HIBIQIIIQ", 39)
            s.qid = Qid(typ, vers, path)
            s.name = self.decS()     # name
            s.uid = self.decS()      # uid
            s.gid = self.decS()      # gid
            s.muid = self.decS()     # muid
            if self.dotu:
                s.extension = self.decS()
                (s.uidnum,
                        s.gidnum,
                        s.muidnum) = self.decF("=III", 12)
            stats.append(s)

    def dec(self, fcall):
        if fcall.type in (Tversion, Rversion):
            fcall.msize = self.dec4()
            fcall.version = self.decS()
        elif fcall.type == Tauth:
            fcall.afid = self.dec4()
            fcall.uname = self.decS()
            fcall.aname = self.decS()
            if self.dotu:
                fcall.uidnum = self.dec4()
        elif fcall.type == Rauth:
            fcall.aqid = self.decQ()
        elif fcall.type == Rerror:
            fcall.ename = self.decS()
            if self.dotu:
                fcall.errno = self.dec4()
        elif fcall.type == Tflush:
            fcall.oldtag = self.dec2()
        elif fcall.type == Tattach:
            fcall.fid = self.dec4()
            fcall.afid = self.dec4()
            fcall.uname = self.decS()
            fcall.aname = self.decS()
            if self.dotu:
                fcall.uidnum = self.dec4()
        elif fcall.type == Rattach:
            fcall.qid = self.decQ()
        elif fcall.type == Twalk:
            fcall.fid = self.dec4()
            fcall.newfid = self.dec4()
            fcall.nwname = self.dec2()
            fcall.wname = [self.decS().decode('utf-8') for n in range(fcall.nwname)]
        elif fcall.type == Rwalk:
            fcall.nwqid = self.dec2()
            fcall.wqid = [self.decQ() for n in range(fcall.nwqid)]
        elif fcall.type == Topen:
            fcall.fid = self.dec4()
            fcall.mode = self.dec1()
        elif fcall.type in (Ropen, Rcreate):
            fcall.qid = self.decQ()
            fcall.iounit = self.dec4()
        elif fcall.type == Tcreate:
            fcall.fid = self.dec4()
            fcall.name = self.decS().decode('utf-8')
            fcall.perm = self.dec4()
            fcall.mode = self.dec1()
            if self.dotu:
                fcall.extension = self.decS().decode('utf-8')
        elif fcall.type == Tread:
            fcall.fid = self.dec4()
            fcall.offset = self.dec8()
            fcall.count = self.dec4()
        elif fcall.type == Rread:
            fcall.data = self.decD()
        elif fcall.type == Twrite:
            fcall.fid = self.dec4()
            fcall.offset = self.dec8()
            fcall.count = self.dec4()
            fcall.data = self.buf.read(fcall.count)
        elif fcall.type == Rwrite:
            fcall.count = self.dec4()
        elif fcall.type in (Tclunk, Tremove, Tstat):
            fcall.fid = self.dec4()
        elif fcall.type in (Rstat, Twstat):
            if fcall.type == Twstat:
                fcall.fid = self.dec4()
            self.decstat(fcall.stat, 1)

        return fcall


def modetostr(mode):
    bits = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"]

    def b(s):
        return bits[(mode >> s) & 7]
    d = "-"
    if mode & DMDIR:
        d = "d"
    elif mode & DMAPPEND:
        d = "a"
    return "%s%s%s%s" % (d, b(6), b(3), b(0))


def open2stat(mode):
    return (mode & 3) |\
            ((mode & OAPPEND) >> 4) |\
            ((mode & OEXCL) >> 5) |\
            ((mode & OTRUNC) << 5)


def open2plan(mode):
    return (mode & 3) |\
            ((mode & os.O_APPEND) << 4) |\
            ((mode & os.O_EXCL) << 5) |\
            ((mode & os.O_TRUNC) >> 5)


def mode2stat(mode):
    return (mode & 0o777) |\
            ((mode & DMDIR ^ DMDIR) >> 16) |\
            ((mode & DMDIR) >> 17) |\
            ((mode & DMSYMLINK) >> 10) |\
            ((mode & DMSYMLINK) >> 12) |\
            ((mode & DMSETUID) >> 8) |\
            ((mode & DMSETGID) >> 8) |\
            ((mode & DMSTICKY) >> 7)


def mode2plan(mode):
    return (mode & 0o777) | \
            ((mode & stat.S_IFDIR) << 17) |\
            ((mode & stat.S_ISUID) << 8) |\
            ((mode & stat.S_ISGID) << 8) |\
            ((mode & stat.S_ISVTX) << 7) |\
            (int(mode == stat.S_IFLNK) << 25)


def hash8(obj):
    return int(abs(hash(obj)))


def otoa(p):
    '''Convert from open() to access()-style args'''
    ret = 0

    np = p & 3
    if np == OREAD:
        ret = AREAD
    elif np == OWRITE:
        ret = AWRITE
    elif np == ORDWR:
        ret = AREAD | AWRITE
    elif np == OEXEC:
        ret = AEXEC

    if(p & OTRUNC):
        ret |= AWRITE

    return ret


def hasperm(f, uid, p):
    '''Verify permissions for access type 'p' to file 'f'. 'p' is of the type
    returned by otoa() above, i.e., should contain the A* flags.

    f should resemble Dir, i.e., should have f.mode, f.uid, f.gid'''
    m = f.mode & 7  # other
    if (p & m) == p:
        return 1

    if f.uid == uid:
        m |= (f.mode >> 6) & 7
        if (p & m) == p:
            return 1
    if f.gid == uid:
        m |= (f.mode >> 3) & 7
        if (p & m) == p:
            return 1
    return 0


class Sock(object):
    """Per-connection state and appropriate read and write methods
    for the Marshaller."""

    def __init__(self, sock, dotu=0, chatty=0):
        self.sock = sock
        self.fids = {}  # fids are per client
        self.reqs = {}  # reqs are per client
        self.uname = None
        self.closing = False
        self.marshal = Marshal9P(dotu=dotu, chatty=chatty)

    def send(self, x):
        self.marshal.send(self, x)

    def recv(self):
        return self.marshal.recv(self)

    def read(self, l):
        if self.closing:
            return ""
        x = self.sock.recv(l)
        while len(x) < l:
            b = self.sock.recv(l - len(x))
            if not b:
                raise EofError("client eof")
            x += b
        return x

    def write(self, buf):
        if self.closing:
            return len(buf)
        if self.sock.send(buf) != len(buf):
            raise Error("short write")

    def fileno(self):
        return self.sock.fileno()

    def delfid(self, fid):
        if fid in self.fids:
            self.fids[fid].ref = self.fids[fid].ref - 1
            if self.fids[fid].ref == 0:
                del self.fids[fid]

    def getfid(self, fid):
        if fid in self.fids:
            return self.fids[fid]
        return None

    def close(self):
        self.sock.close()


class Fcall(object):
    '''# possible values, from p9p's fcall.h
    msize       # Tversion, Rversion
    version     # Tversion, Rversion
    oldtag      # Tflush
    ename       # Rerror
    qid         # Rattach, Ropen, Rcreate
    iounit      # Ropen, Rcreate
    aqid        # Rauth
    afid        # Tauth, Tattach
    uname       # Tauth, Tattach
    aname       # Tauth, Tattach
    perm        # Tcreate
    name        # Tcreate
    mode        # Tcreate, Topen
    newfid      # Twalk
    nwname      # Twalk
    wname       # Twalk, array
    nwqid       # Rwalk
    wqid        # Rwalk, array
    offset      # Tread, Twrite
    count       # Tread, Twrite, Rread
    data        # Twrite, Rread
    nstat       # Twstat, Rstat
    stat        # Twstat, Rstat

    # dotu extensions:
    errno       # Rerror
    extension   # Tcreate
    '''

    def __init__(self, ftype, tag=1, fid=None):
        self.type = ftype
        self.fid = fid
        self.tag = tag
        self.stat = []
        self.iounit = 8192
        self.ename = None
        self.wqid = None

    def tostr(self):
        attr = [x for x in dir(self) if not x.startswith('_') and
                not x.startswith('tostr')]

        ret = ' '.join("%s=%s" % (x, getattr(self, x)) for x in attr)
        ret = cmdName[self.type] + " " + ret

        return repr(ret)


class Qid(object):

    def __init__(self, qtype=None, vers=None, path=None):
        self.type = qtype
        self.vers = vers
        self.path = path

    def __str__(self):
        return '(%x,%x,%x)' % (self.type, self.vers, self.path)

    __repr__ = __str__


class Fid(object):

    def __init__(self, pool, fid, path='', auth=0):
        if fid in pool:
            raise EdupfidError(Edupfid)
        self.fid = fid
        self.ref = 1
        self.omode = -1
        self.auth = auth
        self.uid = None
        self.qid = None
        self.path = path

        pool[fid] = self


class Dir(object):
    # type:         server type
    # dev           server subtype
    #
    # file data:
    # qid           unique id from server
    # mode          permissions
    # atime         last read time
    # mtime         last write time
    # length        file length
    # name
    # uid           owner name
    # gid           group name
    # muid          last modifier name
    #
    # 9P2000.u extensions:
    # uidnum        numeric uid
    # gidnum        numeric gid
    # muidnum       numeric muid
    # *ext          extended info

    def __init__(self, dotu=0, *args, **kwargs):
        self.dotu = dotu
        self.statsz = 0
        # the dotu arguments will be added separately. this is not
        # straightforward but is cleaner.
        if len(args):
            (self.type,
                self.dev,
                self.qid,
                self.mode,
                self.atime,
                self.mtime,
                self.length,
                self.name,
                self.uid,
                self.gid,
                self.muid) = args[:11]

            if dotu:
                (self.extension,
                    self.uidnum,
                    self.gidnum,
                    self.muidnum) = args[11:15]

        if len(kwargs.keys()):
            for i in kwargs.keys():
                setattr(self, i, kwargs[i])

        if not dotu:
            (self.extension,
                self.uidnum,
                self.gidnum,
                self.muidnum) = "", UIDUNDEF, UIDUNDEF, UIDUNDEF

    def tolstr(self, dirname=''):
        if dirname != '':
            dirname = dirname + '/'
        if self.dotu:
            return "%s %d %d %-8d\t\t%s%s" % (
                    modetostr(self.mode), self.uidnum, self.gidnum,
                    self.length, dirname, self.name)
        else:
            return "%s %s %s %-8d\t\t%s%s" % (
                    modetostr(self.mode), self.uid, self.gid,
                    self.length, dirname, self.name)

    def todata(self, marsh):
        marsh.setBuffer()
        marsh.encstat((self, ), 0)
        return marsh.buf.getvalue()


class Req(object):
    def __init__(self, tag, fd=None, ifcall=None, ofcall=None,
            dir=None, oldreq=None, fid=None, afid=None, newfid=None):
        self.tag = tag
        self.fd = fd
        self.ifcall = ifcall
        self.ofcall = ofcall
        self.dir = dir
        self.oldreq = oldreq
        self.fid = fid
        self.afid = afid
        self.newfid = newfid


class Server(object):
    """
    A server interface to the protocol.
    Subclass this to provide service
    """
    chatty = False
    readpool = []
    writepool = []
    activesocks = {}

    def __init__(self, listen, authmode=None, fs=None, user=None,
            dom=None, key=None, chatty=False, dotu=False, msize=8192):
        self.msize = msize

        if authmode is None:
            self.authfs = None
        elif authmode == 'pki':
            from py9p import pki
            self.authfs = pki.AuthFs(key)
        else:
            raise ServerError("unsupported auth mode")

        self.fs = fs
        self.authmode = authmode
        self.dotu = dotu

        self.readpool = []
        self.writepool = []
        self.deferread = {}
        self.deferwrite = {}
        self.user = user
        self.dom = dom
        self.host = listen[0]
        self.port = listen[1]
        self.chatty = chatty

        if self.host[0] == '/':
            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            try:
                os.unlink(self.host)
            except OSError:
                pass
            self.sock.bind(self.host)
            os.chmod(self.host, self.port)
        else:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind((self.host, self.port),)
        self.sock.listen(5)
        self.readpool.append(self.sock)
        if self.chatty:
            print("listening to %s:%d" % (self.host, self.port))

    def mount(self, fs):
        # XXX: for now only allow one mount
        # in the future accept fs/root and
        # handle different filesystems at walk time
        self.fs = fs

    def shutdown(self, sock):
        """Close down a connection."""
        if sock not in self.activesocks:
            return
        s = self.activesocks[sock]
        assert not s.closing  # we looped!
        s.closing = True

        if sock in self.readpool:
            self.readpool.remove(sock)
        if sock in self.writepool:
            self.writepool.remove(sock)

        # find first tag not in use
        tags = [r.ifcall.tag for r in s.reqs]
        tag = [n for n in range(1, 65535) if n not in tags][0]

        # flush all outstanding requests
        for r in s.reqs:
            req = Req(tag)
            req.ifcall = Fcall(Tflush, tag=tag, oldtag=r.ifcall.tag)
            req.ofcall = Fcall(Rflush, tag=tag)
            req.fd = s.fileno()
            req.sock = s
            self.tflush(req)

        # clunk all open fids
        fids = list(s.fids.keys())
        for fid in fids:
            req = Req(tag)
            req.ifcall = Fcall(Tclunk, tag=tag, fid=fid)
            req.ofcall = Fcall(Rclunk, tag=tag)
            req.fd = s.fileno()
            req.sock = s
            self.tclunk(req)

        # flush should have taken care of this
        assert sock not in self.deferwrite and sock not in self.deferread

        sock.close()
        del self.activesocks[sock]

    def serve(self):
        while len(self.readpool) > 0 or len(self.writepool) > 0:
            inr, outr, excr = select.select(self.readpool, self.writepool, [])
            for s in outr:
                if s in self.deferwrite:
                    # this is a fs-delayed req that's just become ready,
                    req = self.deferwrite[s]
                    self.unregwritefd(s)
                    name = cmdName[req.ifcall.type][1:]
                    try:
                        func = getattr(self.fs, name)
                        func(self, req)
                    except:
                        print("error in delayed write response: %s")
                        traceback.print_exc()
                        self.respond(req, "error in delayed response")
                    continue
            for s in inr:
                if s == self.sock:
                    cl, addr = s.accept()
                    self.readpool.append(cl)
                    self.activesocks[cl] = Sock(cl, self.dotu, self.chatty)
                    if self.chatty:
                        print("accepted connection from: %s" % str(addr))
                else:
                    if s in self.deferread:
                        # this is a fs-delayed req that's just become ready,
                        req = self.deferread[s]
                        self.unregreadfd(s)
                        name = cmdName[req.ifcall.type][1:]
                        try:
                            func = getattr(self.fs, name)
                            func(self, req)
                        except:
                            print("error in delayed read response: %s")
                            traceback.print_exc()
                            self.respond(req, "error in delayed response")
                        continue
                    try:
                        self.fromnet(self.activesocks[s])
                    except socket.error as e:
                        if self.chatty:
                            print("socket error: %s" % (e.args[1]))
                            traceback.print_exc()
                        self.shutdown(s)
                    except EofError as e:
                        if self.chatty:
                            print("socket closed: %s" % (e.args[0]))
                        self.readpool.remove(s)
                        self.shutdown(s)
                    except Exception as e:
                        print("error in fromnet (protocol botch?)")
                        traceback.print_exc()
                        print("dropping connection...")
                        self.shutdown(s)

        if self.chatty:
            print("main socket closed")

        return

    def respond(self, req, error=None, errno=None):
        name = 'r' + cmdName[req.ifcall.type][1:]
        if hasattr(self, name):
            func = getattr(self, name)
            try:
                func(req, error)
            except Exception as e:
                print("error in respond: ")
                traceback.print_exc()
                return -1
        else:
            raise ServerError("can not handle message type " +
                    cmdName[req.ifcall.type])

        req.ofcall.tag = req.ifcall.tag
        if error:
            req.ofcall.type = Rerror
            req.ofcall.ename = c9.bytes3(error)
            if not errno:
                errno = ERRUNDEF
            req.ofcall.errno = errno
        s = req.sock
        try:
            s.send(req.ofcall)
        except socket.error as e:
            if self.chatty:
                print("socket error: %s" % (e.args[1]))
                traceback.print_exc()
            self.shutdown(s)
        except EofError as e:
            if self.chatty:
                print("socket closed: %s" % (e.args[0]))
            self.shutdown(s)
        except Exception as e:
            if self.chatty:
                print("socket error: %s" % (str(e.args)))
                traceback.print_exc()
            self.shutdown(s)

        # XXX: unsure whether we need proper flushing semantics from rsc's p9p
        # thing is, we're not threaded.

    def fromnet(self, fd):
        fcall = fd.recv()
        req = Req(fcall.tag)
        req.ifcall = fcall
        req.ofcall = Fcall(fcall.type + 1, fcall.tag)
        req.fd = fd.fileno()
        req.sock = fd

        if req.ifcall.type not in cmdName:
            self.respond(req, "invalid message")

        name = "t" + cmdName[req.ifcall.type][1:]
        if hasattr(self, name):
            func = getattr(self, name)
            try:
                func(req)
            except Error as e:
                if self.chatty:
                    traceback.print_exc()
                self.respond(req, str(e.args[0][1]), e.args[0][0])
            except Exception as e:
                if self.chatty:
                    traceback.print_exc()
                self.respond(req, 'unhandled internal exception: ' +
                        str(e.args[0]))
        else:
            self.respond(req, "unhandled message: %s" % (
                cmdName[req.ifcall.type]))
        return

    def regreadfd(self, fd, req):
        '''Register a file descriptor in the read pool. When a fileserver
        wants to delay responding to a message they can register an fd and
        have it polled for reading. When it's ready, the corresponding 'req'
        will be called'''
        self.deferread[fd] = req
        self.readpool.append(fd)

    def regwritefd(self, fd, req):
        '''Register a file descriptor in the write pool.'''
        self.deferwrite[fd] = req
        self.writepool.append(fd)

    def unregreadfd(self, fd):
        '''Delete a fd registered with regreadfd().'''
        del self.deferread[fd]
        self.readpool.remove(fd)

    def unregwritefd(self, fd):
        '''Delete a fd registered with regwritefd().'''
        del self.deferwrite[fd]
        self.writepool.remove(fd)

    def tversion(self, req):
        if req.ifcall.version[0:2] != b'9P':
            req.ofcall.version = "unknown"
            self.respond(req, None)
            return

        if req.ifcall.version == versionu:
            # dotu is passed to server init to indicate whether dotu
            # will be supported
            #
            # if the server init code was told not to implement dotu
            # then even if the remote wants dotu we must fall back to 9P2000
            if self.dotu:
                req.ofcall.version = versionu
            else:
                req.ofcall.version = version
        else:
            # if somebody requested a later version of the protocol
            # (9Pxxxx.y, for xxxx>2000) then fall back to what we know
            # best: 9P2000;
            #
            # if somebody requested 9Pxxxx for xxxx<2000 then we have no
            # clue what to say and we just keep repeating the same.
            req.ofcall.version = version
            req.sock.marshal.dotu = 0

        req.ofcall.msize = min(req.ifcall.msize, self.msize)
        self.respond(req, None)

    def rversion(self, req, error):
        # self.msize = req.ofcall.msize
        pass

    def tauth(self, req):
        if self.authfs is None:
            self.respond(req, "%s: authentication not required" %
                    (sys.argv[0]))
            return

        try:
            req.afid = Fid(req.sock.fids, req.ifcall.afid, auth=1)
        except EdupfidError:
            self.respond(req, Edupfid)
            return
        req.afid.uname = req.ifcall.uname
        self.authfs.estab(req.afid)
        req.afid.qid = Qid(QTAUTH, 0, hash8('#a'))
        req.ofcall.aqid = req.afid.qid
        self.respond(req, None)

    def rauth(self, req, error):
        if error and req.afid:
            req.sock.delfid(req.afid.fid)

    def tattach(self, req):
        try:
            req.fid = Fid(req.sock.fids, req.ifcall.fid)
        except EdupfidError:
            self.respond(req, Edupfid)
            return

        req.afid = None
        if req.ifcall.afid != NOFID:
            req.afid = req.sock.fids[req.ifcall.afid]
            if not req.afid:
                self.respond(req, Eunknownfid)
                return
            if req.afid.suid != req.ifcall.uname:
                self.respond(req, "not authenticated as %r" % req.ifcall.uname)
                return
            elif self.chatty:
                print("authenticated as %r" % req.ifcall.uname)
        elif self.authmode is not None:
            self.respond(req, 'authentication not complete')

        req.fid.uid = req.ifcall.uname
        req.sock.uname = req.ifcall.uname  # now we know who we are
        if hasattr(self.fs, 'attach'):
            self.fs.attach()
        else:
            req.ofcall.qid = self.fs.root.qid
            req.fid.qid = self.fs.root.qid
            self.respond(req, None)
        return

    def rattach(self, req, error):
        if error and req.fid:
            req.sock.delfid(req.fid.fid)

    def tflush(self, req):
        if hasattr(self.fs, 'flush'):
            self.fs.flush(self, req)
        else:
            req.sock.reqs = []
            self.respond(req, None)

    def rflush(self, req, error):
        if req.oldreq:
            if req.oldreq.responded == 0:
                req.oldreq.nflush = req.oldreq.nflush + 1
                if not hasattr(req.oldreq, 'flush'):
                    req.oldreq.nflush = 0
                    req.oldreq.flush = []
                req.oldreq.nflush = req.oldreq.nflush + 1
                req.oldreq.flush.append(req)
        req.oldreq = None
        return 0

    def twalk(self, req):
        req.ofcall.wqid = []

        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            self.respond(req, Eunknownfid)
            return
        if req.fid.omode != -1:
            self.respond(req, "cannot clone open fid")
            return
        if len(req.ifcall.wname) and not (req.fid.qid.type & QTDIR):
            self.respond(req, Ewalknotdir)
            return
        if req.ifcall.fid != req.ifcall.newfid:
            try:
                req.newfid = Fid(req.sock.fids, req.ifcall.newfid)
            except EdupfidError:
                self.respond(req, Edupfid)
                return
            req.newfid.uid = req.fid.uid
        else:
            req.fid.ref = req.fid.ref + 1
            req.newfid = req.fid

        if len(req.ifcall.wname) == 0:
            req.ofcall.nwqid = 0
            self.respond(req, None)
        elif hasattr(self.fs, 'walk'):
            self.fs.walk(self, req)
        else:
            self.respond(req, "no walk function")

    def rwalk(self, req, error):
        if error or (len(req.ofcall.wqid) < len(req.ifcall.wname) and
                len(req.ifcall.wname) > 0):
            if req.ifcall.fid != req.ifcall.newfid and req.newfid:
                req.sock.delfid(req.ifcall.newfid)
            if len(req.ofcall.wqid) == 0:
                if not error and len(req.ifcall.wname) != 0:
                    req.error = Enotfound
            else:
                req.error = None
        else:
            if len(req.ofcall.wqid) == 0:
                req.newfid.qid = req.fid.qid
            else:
                req.newfid.qid = req.ofcall.wqid[-1]

    def topen(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            self.respond(req, Eunknownfid)
            return
        if req.fid.omode != -1:
            self.respond(req, Ebotch)
            return
        if req.fid.qid.type & QTDIR:
            if (req.ifcall.mode & (~ORCLOSE)) != OREAD:
                self.respond(req, Eisdir)
                return
            # repeating the same bug as p9p?
            if otoa(req.ifcall.mode) != AREAD:
                self.respond(req, Eisdir)
                return

        req.ofcall.qid = req.fid.qid
        req.ofcall.iounit = self.msize - IOHDRSZ
        req.ifcall.acc = [AREAD, AWRITE,
                AREAD | AWRITE, AEXEC][req.ifcall.mode & 3]
        if req.ifcall.mode & OTRUNC:
            req.ifcall.acc |= AWRITE

        if (req.fid.qid.type & QTDIR) and (req.ifcall.acc != AREAD):
            self.respond(req, Eperm)
        if hasattr(self.fs, 'open'):
            self.fs.open(self, req)
        else:
            self.respond(req, None)

    def ropen(self, req, error):
        if error:
            return
        req.fid.omode = req.ifcall.mode
        req.fid.qid = req.ofcall.qid
        if req.ofcall.qid.type & QTDIR:
            req.fid.diroffset = 0

    def tcreate(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            self.respond(req, Eunknownfid)
        elif req.fid.omode != -1:
            self.respond(req, Ebotch)
        elif not (req.fid.qid.type & QTDIR):
            self.respond(req, Ecreatenondir)
        elif hasattr(self.fs, 'create'):
            self.fs.create(self, req)
        else:
            self.respond(req, Enocreate)

    def rcreate(self, req, error):
        if error:
            return
        req.fid.omode = req.ifcall.mode
        req.fid.qid = req.ofcall.qid
        req.ofcall.iounit = self.msize - IOHDRSZ

    def bufread(self, req, buf):
        req.ofcall.data = buf[req.ifcall.offset: req.ifcall.offset +
                req.ifcall.count]
        return self.respond(req, None)

    def tread(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            return self.respond(req, Eunknownfid)
        if req.ifcall.count < 0:
            return self.respond(req, Ebotch)
        if req.ifcall.offset < 0 or ((req.fid.qid.type & QTDIR) and
                (req.ifcall.offset != 0) and
                (req.ifcall.offset != req.fid.diroffset)):
            return self.respond(req, Ebadoffset)
        if req.fid.qid.type & QTAUTH and self.authfs:
            self.authfs.read(self, req)
            return
        # auth Tread goes w/o omode, there was no open()
        if req.fid.omode == -1:
            return self.respond(req, Eopen)

        if req.ifcall.count > self.msize - IOHDRSZ:
            req.ifcall.count = self.msize - IOHDRSZ
        o = req.fid.omode & 3
        if o != OREAD and o != ORDWR and o != OEXEC:
            return self.respond(req, Ebotch)
        if hasattr(self.fs, 'read'):
            self.fs.read(self, req)
        else:
            self.respond(req, 'no server read function')

    def rread(self, req, error):
        if error:
            return

        if req.fid.qid.type & QTDIR:
            data = b""
            for x in req.ofcall.stat:
                ndata = x.todata(req.sock.marshal)
                if (len(data) - req.ifcall.offset) + \
                        len(ndata) < req.ifcall.count:
                    data = data + ndata
                else:
                    break
            req.ofcall.data = data[req.ifcall.offset:]
            req.fid.diroffset = req.ifcall.offset + len(req.ofcall.data)

    def twrite(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            return self.respond(req, Eunknownfid)
        if req.ifcall.count < 0 or req.ifcall.offset < 0:
            return self.respond(req, Ebotch)
        if req.fid.qid.type & QTAUTH and self.authfs:
            self.authfs.write(self, req)
            return
        # auth Tread goes w/o omode, there was no open()
        if req.fid.omode == -1:
            return self.respond(req, Eopen)

        if req.ifcall.count > self.msize - IOHDRSZ:
            req.ifcall.count = self.msize - IOHDRSZ
        o = req.fid.omode & 3
        if o != OWRITE and o != ORDWR:
            return self.respond(req,
                    "write on fid with open mode 0x%ux" % req.fid.omode)
        if hasattr(self.fs, 'write'):
            self.fs.write(self, req)
        else:
            self.respond(req, 'no server write function')

    def rwrite(self, req, error):
        return

    def tclunk(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            return self.respond(req, Eunknownfid)
        if hasattr(self.fs, 'clunk') and not (req.fid.qid.type & QTAUTH):
            self.fs.clunk(self, req)
        else:
            self.respond(req, None)
        req.sock.delfid(req.ifcall.fid)

    def rclunk(self, req, error):
        return

    def tremove(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            return self.respond(req, Eunknownfid)
        if hasattr(self.fs, 'remove'):
            self.fs.remove(self, req)
        else:
            self.respond(req, Enoremove)

    def rremove(self, req, error):
        req.sock.delfid(req.ifcall.fid)
        return

    def tstat(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        req.ofcall.stat = []
        if not req.fid:
            return self.respond(req, Eunknownfid)
        if hasattr(self.fs, 'stat'):
            self.fs.stat(self, req)
        else:
            self.respond(req, Enostat)

    def rstat(self, req, error):
        if error:
            return

    def twstat(self, req):
        req.fid = req.sock.getfid(req.ifcall.fid)
        if not req.fid:
            return self.respond(req, Eunknownfid)
        if hasattr(self.fs, 'wstat'):
            self.fs.wstat(self, req)
        else:
            self.respond(req, Enowstat)

    def rwstat(self, req, error):
        return


class Credentials(object):
    def __init__(self, user, authmode=None, passwd=None,
            keyfile=None, key=None):
        self.user = c9.bytes3(user) if isinstance(user, str) else user
        self.passwd = c9.bytes3(passwd) if isinstance(passwd, str) else passwd
        self.key = key
        self.authmode = authmode
        if self.authmode == "pki":
            import pki
            self.key = pki.getprivkey(user, keyfile, passwd)


class Client(object):
    """
    A client interface to the protocol.
    """
    AFID = 10
    ROOT = 11
    CWD = 12
    F = 13

    path = ''  # for 'getwd' equivalent

    def __init__(self, fd, credentials, authsrv=None, chatty=0, dotu=0,
            msize=8192):
        self.credentials = credentials
        self.dotu = dotu
        self.msize = msize
        self.fd = Sock(fd, dotu, chatty)
        self.login(authsrv, credentials)

    def _rpc(self, fcall):
        if fcall.type == Tversion:
            fcall.tag = NOTAG
        self.fd.send(fcall)
        try:
            ifcall = self.fd.recv()
        except (KeyboardInterrupt, Exception):
            # try to flush the operation, then rethrow exception
            if fcall.type != Tflush:
                try:
                    self._flush(fcall.tag, fcall.tag + 1)
                except Exception:
                    pass
            raise
        if ifcall.tag != fcall.tag:
            raise RpcError("invalid tag received")
        if ifcall.type == Rerror:
            raise RpcError(ifcall.ename)
        if ifcall.type != fcall.type + 1:
            raise ClientError("incorrect reply from server: %r" %
                    [fcall.type, fcall.tag])
        return ifcall

    # protocol calls; part of 9p
    # should be private functions, really
    def _version(self, msize, version):
        fcall = Fcall(Tversion)
        self.msize = msize
        fcall.msize = msize
        fcall.version = version
        return self._rpc(fcall)

    def _auth(self, afid, uname, aname):
        fcall = Fcall(Tauth)
        fcall.afid = afid
        fcall.uname = uname
        fcall.aname = aname
        fcall.uidnum = 0
        return self._rpc(fcall)

    def _attach(self, fid, afid, uname, aname):
        fcall = Fcall(Tattach)
        fcall.fid = fid
        fcall.afid = afid
        fcall.uname = uname
        fcall.aname = aname
        fcall.uidnum = 0
        return self._rpc(fcall)

    def _walk(self, fid, newfid, wnames):
        fcall = Fcall(Twalk)
        fcall.fid = fid
        fcall.newfid = newfid
        fcall.wname = [c9.bytes3(x) for x in wnames]
        return self._rpc(fcall)

    def _open(self, fid, mode):
        fcall = Fcall(Topen)
        fcall.fid = fid
        fcall.mode = mode
        return self._rpc(fcall)

    def _create(self, fid, name, perm, mode, extension=b""):
        fcall = Fcall(Tcreate)
        fcall.fid = fid
        fcall.name = name
        fcall.perm = perm
        fcall.mode = mode
        fcall.extension = extension
        return self._rpc(fcall)

    def _read(self, fid, off, count):
        fcall = Fcall(Tread)
        fcall.fid = fid
        fcall.offset = off
        if count > self.msize - IOHDRSZ:
            count = self.msize - IOHDRSZ
        fcall.count = count
        return self._rpc(fcall)

    def _write(self, fid, off, data):
        fcall = Fcall(Twrite)
        fcall.fid = fid
        fcall.offset = off
        fcall.data = data
        return self._rpc(fcall)

    def _clunk(self, fid):
        fcall = Fcall(Tclunk)
        fcall.fid = fid
        return self._rpc(fcall)

    def _remove(self, fid):
        fcall = Fcall(Tremove)
        fcall.fid = fid
        return self._rpc(fcall)

    def _stat(self, fid):
        fcall = Fcall(Tstat)
        fcall.fid = fid
        return self._rpc(fcall)

    def _wstat(self, fid, stats):
        fcall = Fcall(Twstat)
        fcall.fid = fid
        fcall.stat = stats
        return self._rpc(fcall)

    def _flush(self, tag, oldtag):
        fcall = Fcall(Tflush, tag=tag)
        fcall.oldtag = tag
        return self._rpc(fcall)

    def _fullclose(self):
        self._clunk(self.ROOT)
        self._clunk(self.CWD)
        self.fd.close()

    def login(self, authsrv, credentials):
        if self.dotu:
            ver = versionu
        else:
            ver = version
        fcall = self._version(self.msize, ver)
        self.msize = fcall.msize
        if fcall.version != ver:
            raise VersionError("version mismatch: %r" % fcall.version)

        fcall.afid = self.AFID
        try:
            rfcall = self._auth(fcall.afid, credentials.user, b'')
        except RpcError as e:
            fcall.afid = NOFID

        if fcall.afid != NOFID:
            fcall.aqid = rfcall.aqid

            if credentials.authmode is None:
                raise ClientError('no authentication method')
            elif credentials.authmode == 'pki':
                import pki
                pki.clientAuth(self, fcall, credentials)
            else:
                raise ClientError('unknown authentication method: %s' %
                        credentials.authmode)

        self._attach(self.ROOT, fcall.afid, credentials.user, b'')
        if fcall.afid != NOFID:
            self._clunk(fcall.afid)
        self._walk(self.ROOT, self.CWD, [])
        self.path = '/'

    # user accessible calls, the actual implementation of a client
    def close(self):
        self._clunk(self.F)

    def walk(self, pstr=''):
        root = self.CWD
        if pstr == '':
            path = []
        elif pstr.find('/') == -1:
            path = [pstr]
        else:
            path = pstr.split('/')
            if path[0] == '':
                root = self.ROOT
                path = path[1:]
            path = list(filter(None, path))
        try:
            fcall = self._walk(root, self.F, path)
        except RpcError:
            raise

        if len(fcall.wqid) < len(path):
            raise RpcError('incomplete walk (%d out of %d)' %
                    (len(fcall.wqid), len(path)))
        return fcall.wqid

    def open(self, pstr='', mode=0):
        if self.walk(pstr) is None:
            return
        self.pos = 0
        try:
            fcall = self._open(self.F, mode)
        except RpcError:
            self.close()
            raise
        return fcall

    def create(self, pstr, perm=0o644, mode=1):
        p = pstr.split('/')
        pstr2, name = '/'.join(p[:-1]), p[-1]
        if self.walk(pstr2) is None:
            return
        self.pos = 0
        try:
            return self._create(self.F, name, perm, mode)
        except RpcError:
            self.close()
            raise

    def rm(self, pstr):
        self.open(pstr)
        try:
            self._remove(self.F)
        except RpcError:
            raise

    def read(self, l):
        try:
            fcall = self._read(self.F, self.pos, l)
            buf = fcall.data
        except RpcError:
            self.close()
            raise

        self.pos += len(buf)
        return buf

    def write(self, buf):
        try:
            l = self._write(self.F, self.pos, buf).count
            self.pos += l
            return l
        except RpcError:
            self.close()
            raise

    def stat(self, pstr):
        if self.walk(pstr) is None:
            return
        try:
            fc = self._stat(self.F)
        finally:
            self.close()
        return fc.stat

    def lsdir(self):
        ret = []
        while 1:
            buf = self.read(self.msize)
            if len(buf) == 0:
                break
            p9 = Marshal9P()
            p9.setBuffer(buf)
            p9.buf.seek(0)
            fcall = Fcall(Rstat)
            try:
                p9.decstat(fcall.stat, 0)
            except:
                self.close()
                print('unexpected decstat error:')
                traceback.print_exc()
                raise
            ret += fcall.stat
        return ret

    def ls(self, long=0, args=[]):
        ret = []

        if len(args) == 0:
            if self.open() is None:
                return
            if long:
                ret = [z.tolstr() for z in self.lsdir()]
            else:
                ret = [z.name for z in self.lsdir()]
            self.close()
        else:
            for x in args:
                stat = self.stat(x)
                if not stat:
                    return  # stat already printed a message
                if len(stat) == 1:
                    if stat[0].mode & DMDIR:
                        self.open(x)
                        lsd = self.lsdir()
                        if long:
                            ret += [z.tolstr() for z in lsd]
                        else:
                            ret += [x + '/' + z.name for z in lsd]
                        self.close()
                    else:
                        if long:
                            # we already have full path+name, but tolstr()
                            # wants to append the name to the end anyway, so
                            # strip the last basename out to form identical
                            # path+name
                            ret.append(stat[0].tolstr(
                                x[0:-len(stat[0].name) - 1]))
                        else:
                            ret.append(x)
                else:
                    print('%s: returned multiple stats (internal error)' % x)
        return ret

    def cd(self, pstr):
        q = self.walk(pstr)
        if q is None:
            return 0
        if q and not (q[-1].type & QTDIR):
            print("%s: not a directory" % pstr)
            self.close()
            return 0
        self.F, self.CWD = self.CWD, self.F
        self.close()
        return 1
