232 lines · 7.1 KB · Python
RawDownload
# styxserver.py - Network code for handling Styx (9P2000) messages.## Copyright (C) 2018 David Boddie <david@boddie.org.uk>## This program is free software: you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation, either version 3 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the# GNU General Public License for more details.## You should have received a copy of the GNU General Public License# along with this program.  If not, see <http://www.gnu.org/licenses/>. import socketimport styx class StyxServerError(Exception):    pass class StyxServer:     MAX_MSG_SIZE = 0        def __init__(self, store):            self.store = store        self.clients = {}        def serve(self, host, port):            s = socket.socket()        s.bind((host, port))        s.listen(1)                while True:                    conn, client = s.accept()            self.clients[client] = self.store                        while client in self.clients:                            message = styx.decode(sock=conn)                                try:                    handler = self.handlers[message.code]                    reply = handler(self, client, message)                except KeyError:                    reply = styx.Rerror(message.tag, "Unsupported message.")                except StyxServerError as e:                    reply = styx.Rerror(message.tag, e.message)                except socket.error:                    # The connection was probably closed by the client.                    break                                reply.encode(conn)        def Tversion(self, client, msg):            return styx.Rversion(msg.tag, msg.msize, msg.version)        def Tattach(self, client, msg):            store = self.clients[client]                qid = store.get_root_qid(msg.fid, msg.afid, msg.uname, msg.aname)        if qid == None:            return styx.Rerror(msg.tag, "Root not found.")                return styx.Rattach(msg.tag, qid)        def Tstat(self, client, msg):            store = self.clients[client]                s = store.stat(msg.fid)        if s == None:            return styx.Rerror(msg.tag, "Not found.")                return styx.Rstat(msg.tag, s)        def Twalk(self, client, msg):            store = self.clients[client]                # The fid must have an existing qid but not be in use for open or create.        qid, path = store.get_qid_path(msg.fid)                # Generate qids for each of the elements in the path, starting from the        # path corresponding to the current fid.        qids = []        new_path = path.split("/")                for element in msg.wname:                    if element == "..":                # Handle parent directories as path elements.                if new_path: new_path.pop()            else:                new_path.append(element)                        # Update the qid variable, create qids for each intermediate            # path, and record the qids created.            qid = store.make_qid("/".join(new_path))                        if qid == None:                if len(qids) > 0:                    return styx.Rwalk(msg.tag, qids)                else:                    return styx.Rerror(msg.tag, "Not found.")                        qids.append(qid)                # Set the qid and path for the newfid passed by the caller.        store.set_qid_path(msg.newfid, qid, "/".join(new_path))                return styx.Rwalk(msg.tag, qids)        def Topen(self, client, msg):            store = self.clients[client]                # The fid must have an existing qid.        qid, path = store.get_qid_path(msg.fid)                if not store.open(msg.fid, msg.mode):            return styx.Rerror(msg.tag, "Cannot open file.")                return styx.Ropen(msg.tag, qid, self.MAX_MSG_SIZE)        def Tcreate(self, client, msg):            store = self.clients[client]                # The fid must have an existing qid that corresponds to a directory        # and cannot be itself in use.        qid, path = store.get_qid_path(msg.fid)                new_qid = store.create(msg.fid, msg.name, msg.perm)        if new_qid == False:            return styx.Rerror(msg.tag, "Cannot create file.")                # The fid now refers to the newly open file or directory.        if not store.open(msg.fid, msg.mode):            return styx.Rerror(msg.tag, "Cannot open file.")                return styx.Rcreate(msg.tag, new_qid, self.MAX_MSG_SIZE)        def Tread(self, client, msg):            store = self.clients[client]                # The fid must have an existing qid.        data = store.read(msg.fid, msg.offset, msg.count)                return styx.Rread(msg.tag, data)        def Twrite(self, client, msg):            store = self.clients[client]                # The fid must have an existing qid.        count = store.write(msg.fid, msg.offset, msg.data)                if count == -1:            return styx.Rerror(msg.tag, "Not a file.")        elif count != len(msg.data):            return styx.Rerror(msg.tag, "Failed to write data.")                return styx.Rwrite(msg.tag, count)        def Tclunk(self, client, msg):            store = self.clients[client]                # Release/free the qid.        store.free_qid_path(msg.fid)                # Additionally, if the fid refers to the root of the file system then        # remove the client from the clients dictionary to disconnect it.        if msg.fid == store.root_fid:            del self.clients[client]                return styx.Rclunk(msg.tag)        def Tremove(self, client, msg):            store = self.clients[client]                # Remove the file and clunk it whether the remove was successful or not.        result = store.remove(msg.fid)                if result == True:            store.free_qid_path(msg.fid)            return styx.Rremove(msg.tag)        else:            store.free_qid_path(msg.fid)            return styx.Rerror(msg.tag, result)        def Tflush(self, client, msg):            store = self.clients[client]                # If we supported a queue of commands then we would remove any pending        # commands with tags matching msg.oldtag.                return styx.Rflush(msg.tag)        def Twstat(self, client, msg):            store = self.clients[client]                store.wstat(msg.fid, msg.stat)        return styx.Rwstat(msg.tag)        handlers = {        styx.Tversion.code: Tversion,        styx.Tattach.code: Tattach,        styx.Tstat.code: Tstat,        styx.Twalk.code: Twalk,        styx.Topen.code: Topen,        styx.Tcreate.code: Tcreate,        styx.Tread.code: Tread,        styx.Twrite.code: Twrite,        styx.Tclunk.code: Tclunk,        styx.Tremove.code: Tremove,        styx.Twstat.code: Twstat,        styx.Tflush.code: Tflush        }