Unified · Split
| @@ -17,211 +17,8 @@ · 16 unmodified lines | ||
| 17 | 17 | # You should have received a copy of the GNU General Public License |
| 18 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 19 | 19 | |
| 20 | −import os, shutil, socket, stat, sys | |
| 21 | −import styx | |
| 22 | − | |
| 23 | −class StyxFSError(Exception): | |
| 24 | − pass | |
| 25 | − | |
| 26 | −class StyxFSServer: | |
| 27 | − | |
| 28 | − MAX_MSG_SIZE = 0 | |
| 29 | − | |
| 30 | − def __init__(self, directory): | |
| 31 | − | |
| 32 | − self.dir = directory | |
| 33 | − self.clients = {} | |
| 34 | − | |
| 35 | − def serve(self, host, port): | |
| 36 | − | |
| 37 | − s = socket.socket() | |
| 38 | − s.bind((host, port)) | |
| 39 | − s.listen(1) | |
| 40 | − | |
| 41 | − while True: | |
| 42 | − | |
| 43 | − conn, client = s.accept() | |
| 44 | − self.clients[client] = FileStore(self.dir) | |
| 45 | − | |
| 46 | − while client in self.clients: | |
| 47 | − | |
| 48 | − message = styx.decode(sock=conn) | |
| 49 | − print message | |
| 50 | − | |
| 51 | − try: | |
| 52 | − handler = self.handlers[message.code] | |
| 53 | − reply = handler(self, conn, client, message) | |
| 54 | − except KeyError: | |
| 55 | − reply = styx.Rerror(message.tag, "Unsupported message.") | |
| 56 | − except StyxFSError, e: | |
| 57 | − reply = styx.Rerror(message.tag, e.message) | |
| 58 | − except socket.error: | |
| 59 | − # The connection was probably closed by the client. | |
| 60 | − break | |
| 61 | − | |
| 62 | − print reply | |
| 63 | − reply.encode(conn) | |
| 64 | − | |
| 65 | − def Tversion(self, conn, client, msg): | |
| 66 | − | |
| 67 | − return styx.Rversion(msg.tag, msg.msize, msg.version) | |
| 68 | − | |
| 69 | − def Tattach(self, conn, client, msg): | |
| 70 | − | |
| 71 | − store = self.clients[client] | |
| 72 | − | |
| 73 | − qid = store.get_root_qid(msg.fid, msg.afid, msg.uname, msg.aname) | |
| 74 | − return styx.Rattach(msg.tag, qid) | |
| 75 | − | |
| 76 | − def Tstat(self, conn, client, msg): | |
| 77 | − | |
| 78 | − store = self.clients[client] | |
| 79 | − | |
| 80 | − s = store.stat(msg.fid) | |
| 81 | − return styx.Rstat(msg.tag, s) | |
| 82 | − | |
| 83 | − def Twalk(self, conn, client, msg): | |
| 84 | − | |
| 85 | − store = self.clients[client] | |
| 86 | − | |
| 87 | − # The fid must have an existing qid but not be in use for open or create. | |
| 88 | − qid, path = store.get_qid_path(msg.fid) | |
| 89 | − | |
| 90 | − # Generate qids for each of the elements in the path, starting from the | |
| 91 | − # path corresponding to the current fid. | |
| 92 | − qids = [] | |
| 93 | − new_path = path.split("/") | |
| 94 | − try: | |
| 95 | − for element in msg.wname: | |
| 96 | − | |
| 97 | − if element == "..": | |
| 98 | − # Handle parent directories as path elements. | |
| 99 | − if new_path: new_path.pop() | |
| 100 | − else: | |
| 101 | − new_path.append(element) | |
| 102 | − | |
| 103 | − # Update the qid variable, create qids for each intermediate | |
| 104 | − # path, and record the qids created. | |
| 105 | − qid = store.make_qid("/".join(new_path)) | |
| 106 | − qids.append(qid) | |
| 107 | − | |
| 108 | − except OSError: | |
| 109 | − if len(qids) > 0: | |
| 110 | − return styx.Rwalk(msg.tag, qids) | |
| 111 | − else: | |
| 112 | − return styx.Rerror(msg.tag, "Not found.") | |
| 113 | − | |
| 114 | − # Set the qid and path for the newfid passed by the caller. | |
| 115 | − store.set_qid_path(msg.newfid, qid, "/".join(new_path)) | |
| 116 | − | |
| 117 | − return styx.Rwalk(msg.tag, qids) | |
| 118 | − | |
| 119 | − def Topen(self, conn, client, msg): | |
| 120 | − | |
| 121 | − store = self.clients[client] | |
| 122 | − | |
| 123 | − # The fid must have an existing qid. | |
| 124 | − qid, path = store.get_qid_path(msg.fid) | |
| 125 | − | |
| 126 | − if not store.open(msg.fid, msg.mode): | |
| 127 | − return styx.Rerror(msg.tag, "Cannot open file.") | |
| 128 | − | |
| 129 | − return styx.Ropen(msg.tag, qid, self.MAX_MSG_SIZE) | |
| 130 | − | |
| 131 | − def Tcreate(self, conn, client, msg): | |
| 132 | − | |
| 133 | − store = self.clients[client] | |
| 134 | − | |
| 135 | − # The fid must have an existing qid that corresponds to a directory | |
| 136 | − # and cannot be itself in use. | |
| 137 | − qid, path = store.get_qid_path(msg.fid) | |
| 138 | − | |
| 139 | − new_qid = store.create(msg.fid, msg.name, msg.perm) | |
| 140 | − if new_qid == False: | |
| 141 | − return styx.Rerror(msg.tag, "Cannot create file.") | |
| 142 | − | |
| 143 | − # The fid now refers to the newly open file or directory. | |
| 144 | − if not store.open(msg.fid, msg.mode): | |
| 145 | − return styx.Rerror(msg.tag, "Cannot open file.") | |
| 146 | − | |
| 147 | − return styx.Rcreate(msg.tag, new_qid, self.MAX_MSG_SIZE) | |
| 148 | − | |
| 149 | − def Tread(self, conn, client, msg): | |
| 150 | − | |
| 151 | − store = self.clients[client] | |
| 152 | − | |
| 153 | − # The fid must have an existing qid. | |
| 154 | − data = store.read(msg.fid, msg.offset, msg.count) | |
| 155 | − | |
| 156 | − return styx.Rread(msg.tag, data) | |
| 157 | − | |
| 158 | − def Twrite(self, conn, client, msg): | |
| 159 | − | |
| 160 | − store = self.clients[client] | |
| 161 | − | |
| 162 | − # The fid must have an existing qid. | |
| 163 | − count = store.write(msg.fid, msg.offset, msg.data) | |
| 164 | − | |
| 165 | − return styx.Rwrite(msg.tag, count) | |
| 166 | − | |
| 167 | − def Tclunk(self, conn, client, msg): | |
| 168 | − | |
| 169 | − store = self.clients[client] | |
| 170 | − | |
| 171 | − # Release/free the qid. | |
| 172 | − store.free_qid_path(msg.fid) | |
| 173 | − | |
| 174 | − # Additionally, if the fid refers to the root of the file system then | |
| 175 | − # remove the client from the clients dictionary to disconnect it. | |
| 176 | − if msg.fid == store.root_fid: | |
| 177 | − del self.clients[client] | |
| 178 | − | |
| 179 | − return styx.Rclunk(msg.tag) | |
| 180 | − | |
| 181 | − def Tremove(self, conn, client, msg): | |
| 182 | − | |
| 183 | − store = self.clients[client] | |
| 184 | − | |
| 185 | − # Remove the file and clunk it whether the remove was successful or not. | |
| 186 | − result = store.remove(msg.fid) | |
| 187 | − | |
| 188 | − if result == True: | |
| 189 | − store.free_qid_path(msg.fid) | |
| 190 | − return styx.Rremove(msg.tag) | |
| 191 | − else: | |
| 192 | − store.free_qid_path(msg.fid) | |
| 193 | − return styx.Rerror(msg.tag, result) | |
| 194 | − | |
| 195 | − def Tflush(self, conn, client, msg): | |
| 196 | − | |
| 197 | − store = self.clients[client] | |
| 198 | − | |
| 199 | − # If we supported a queue of commands then we would remove any pending | |
| 200 | − # commands with tags matching msg.oldtag. | |
| 201 | − | |
| 202 | − return styx.Rflush(msg.tag) | |
| 203 | − | |
| 204 | − def Twstat(self, conn, client, msg): | |
| 205 | − | |
| 206 | − store = self.clients[client] | |
| 207 | − | |
| 208 | − s = store.wstat(msg.fid, msg.stat) | |
| 209 | − return styx.Rwstat(msg.tag) | |
| 210 | − | |
| 211 | − handlers = { | |
| 212 | − styx.Tversion.code: Tversion, | |
| 213 | − styx.Tattach.code: Tattach, | |
| 214 | − styx.Tstat.code: Tstat, | |
| 215 | − styx.Twalk.code: Twalk, | |
| 216 | − styx.Topen.code: Topen, | |
| 217 | − styx.Tcreate.code: Tcreate, | |
| 218 | − styx.Tread.code: Tread, | |
| 219 | − styx.Twrite.code: Twrite, | |
| 220 | − styx.Tclunk.code: Tclunk, | |
| 221 | − styx.Tremove.code: Tremove, | |
| 222 | − styx.Twstat.code: Twstat | |
| 223 | − } | |
| 224 | − | |
| 20 | +import os, stat, sys | |
| 21 | +import styx, styxserver | |
| 225 | 22 | |
| 226 | 23 | class FileStore: |
| 227 | 24 | |
| @@ -462,5 +259,6 @@ if __name__ == "__main__": · 234 unmodified lines | ||
| 462 | 259 | directory = sys.argv[1] |
| 463 | 260 | port = int(sys.argv[2]) |
| 464 | 261 | |
| 465 | − server = StyxFSServer(directory) | |
| 262 | + store = FileStore(directory) | |
| 263 | + server = styxserver.StyxServer(store) | |
| 466 | 264 | server.serve("", port) |
| @@ -0,0 +1,219 @@ | ||
| 1 | +# styx.py - Classes to decode and encode Styx (9P2000) messages. | |
| 2 | +# | |
| 3 | +# Copyright (C) 2018 David Boddie <david@boddie.org.uk> | |
| 4 | +# | |
| 5 | +# This program is free software: you can redistribute it and/or modify | |
| 6 | +# it under the terms of the GNU General Public License as published by | |
| 7 | +# the Free Software Foundation, either version 3 of the License, or | |
| 8 | +# (at your option) any later version. | |
| 9 | +# | |
| 10 | +# This program is distributed in the hope that it will be useful, | |
| 11 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | +# GNU General Public License for more details. | |
| 14 | +# | |
| 15 | +# You should have received a copy of the GNU General Public License | |
| 16 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 17 | + | |
| 18 | +import socket | |
| 19 | +import styx | |
| 20 | + | |
| 21 | +class StyxServerError(Exception): | |
| 22 | + pass | |
| 23 | + | |
| 24 | +class StyxServer: | |
| 25 | + | |
| 26 | + MAX_MSG_SIZE = 0 | |
| 27 | + | |
| 28 | + def __init__(self, store): | |
| 29 | + | |
| 30 | + self.store = store | |
| 31 | + self.clients = {} | |
| 32 | + | |
| 33 | + def serve(self, host, port): | |
| 34 | + | |
| 35 | + s = socket.socket() | |
| 36 | + s.bind((host, port)) | |
| 37 | + s.listen(1) | |
| 38 | + | |
| 39 | + while True: | |
| 40 | + | |
| 41 | + conn, client = s.accept() | |
| 42 | + self.clients[client] = self.store | |
| 43 | + | |
| 44 | + while client in self.clients: | |
| 45 | + | |
| 46 | + message = styx.decode(sock=conn) | |
| 47 | + | |
| 48 | + try: | |
| 49 | + handler = self.handlers[message.code] | |
| 50 | + reply = handler(self, conn, client, message) | |
| 51 | + except KeyError: | |
| 52 | + reply = styx.Rerror(message.tag, "Unsupported message.") | |
| 53 | + except StyxServerError, e: | |
| 54 | + reply = styx.Rerror(message.tag, e.message) | |
| 55 | + except socket.error: | |
| 56 | + # The connection was probably closed by the client. | |
| 57 | + break | |
| 58 | + | |
| 59 | + reply.encode(conn) | |
| 60 | + | |
| 61 | + def Tversion(self, conn, client, msg): | |
| 62 | + | |
| 63 | + return styx.Rversion(msg.tag, msg.msize, msg.version) | |
| 64 | + | |
| 65 | + def Tattach(self, conn, client, msg): | |
| 66 | + | |
| 67 | + store = self.clients[client] | |
| 68 | + | |
| 69 | + qid = store.get_root_qid(msg.fid, msg.afid, msg.uname, msg.aname) | |
| 70 | + return styx.Rattach(msg.tag, qid) | |
| 71 | + | |
| 72 | + def Tstat(self, conn, client, msg): | |
| 73 | + | |
| 74 | + store = self.clients[client] | |
| 75 | + | |
| 76 | + s = store.stat(msg.fid) | |
| 77 | + return styx.Rstat(msg.tag, s) | |
| 78 | + | |
| 79 | + def Twalk(self, conn, client, msg): | |
| 80 | + | |
| 81 | + store = self.clients[client] | |
| 82 | + | |
| 83 | + # The fid must have an existing qid but not be in use for open or create. | |
| 84 | + qid, path = store.get_qid_path(msg.fid) | |
| 85 | + | |
| 86 | + # Generate qids for each of the elements in the path, starting from the | |
| 87 | + # path corresponding to the current fid. | |
| 88 | + qids = [] | |
| 89 | + new_path = path.split("/") | |
| 90 | + try: | |
| 91 | + for element in msg.wname: | |
| 92 | + | |
| 93 | + if element == "..": | |
| 94 | + # Handle parent directories as path elements. | |
| 95 | + if new_path: new_path.pop() | |
| 96 | + else: | |
| 97 | + new_path.append(element) | |
| 98 | + | |
| 99 | + # Update the qid variable, create qids for each intermediate | |
| 100 | + # path, and record the qids created. | |
| 101 | + qid = store.make_qid("/".join(new_path)) | |
| 102 | + qids.append(qid) | |
| 103 | + | |
| 104 | + except OSError: | |
| 105 | + if len(qids) > 0: | |
| 106 | + return styx.Rwalk(msg.tag, qids) | |
| 107 | + else: | |
| 108 | + return styx.Rerror(msg.tag, "Not found.") | |
| 109 | + | |
| 110 | + # Set the qid and path for the newfid passed by the caller. | |
| 111 | + store.set_qid_path(msg.newfid, qid, "/".join(new_path)) | |
| 112 | + | |
| 113 | + return styx.Rwalk(msg.tag, qids) | |
| 114 | + | |
| 115 | + def Topen(self, conn, client, msg): | |
| 116 | + | |
| 117 | + store = self.clients[client] | |
| 118 | + | |
| 119 | + # The fid must have an existing qid. | |
| 120 | + qid, path = store.get_qid_path(msg.fid) | |
| 121 | + | |
| 122 | + if not store.open(msg.fid, msg.mode): | |
| 123 | + return styx.Rerror(msg.tag, "Cannot open file.") | |
| 124 | + | |
| 125 | + return styx.Ropen(msg.tag, qid, self.MAX_MSG_SIZE) | |
| 126 | + | |
| 127 | + def Tcreate(self, conn, client, msg): | |
| 128 | + | |
| 129 | + store = self.clients[client] | |
| 130 | + | |
| 131 | + # The fid must have an existing qid that corresponds to a directory | |
| 132 | + # and cannot be itself in use. | |
| 133 | + qid, path = store.get_qid_path(msg.fid) | |
| 134 | + | |
| 135 | + new_qid = store.create(msg.fid, msg.name, msg.perm) | |
| 136 | + if new_qid == False: | |
| 137 | + return styx.Rerror(msg.tag, "Cannot create file.") | |
| 138 | + | |
| 139 | + # The fid now refers to the newly open file or directory. | |
| 140 | + if not store.open(msg.fid, msg.mode): | |
| 141 | + return styx.Rerror(msg.tag, "Cannot open file.") | |
| 142 | + | |
| 143 | + return styx.Rcreate(msg.tag, new_qid, self.MAX_MSG_SIZE) | |
| 144 | + | |
| 145 | + def Tread(self, conn, client, msg): | |
| 146 | + | |
| 147 | + store = self.clients[client] | |
| 148 | + | |
| 149 | + # The fid must have an existing qid. | |
| 150 | + data = store.read(msg.fid, msg.offset, msg.count) | |
| 151 | + | |
| 152 | + return styx.Rread(msg.tag, data) | |
| 153 | + | |
| 154 | + def Twrite(self, conn, client, msg): | |
| 155 | + | |
| 156 | + store = self.clients[client] | |
| 157 | + | |
| 158 | + # The fid must have an existing qid. | |
| 159 | + count = store.write(msg.fid, msg.offset, msg.data) | |
| 160 | + | |
| 161 | + return styx.Rwrite(msg.tag, count) | |
| 162 | + | |
| 163 | + def Tclunk(self, conn, client, msg): | |
| 164 | + | |
| 165 | + store = self.clients[client] | |
| 166 | + | |
| 167 | + # Release/free the qid. | |
| 168 | + store.free_qid_path(msg.fid) | |
| 169 | + | |
| 170 | + # Additionally, if the fid refers to the root of the file system then | |
| 171 | + # remove the client from the clients dictionary to disconnect it. | |
| 172 | + if msg.fid == store.root_fid: | |
| 173 | + del self.clients[client] | |
| 174 | + | |
| 175 | + return styx.Rclunk(msg.tag) | |
| 176 | + | |
| 177 | + def Tremove(self, conn, client, msg): | |
| 178 | + | |
| 179 | + store = self.clients[client] | |
| 180 | + | |
| 181 | + # Remove the file and clunk it whether the remove was successful or not. | |
| 182 | + result = store.remove(msg.fid) | |
| 183 | + | |
| 184 | + if result == True: | |
| 185 | + store.free_qid_path(msg.fid) | |
| 186 | + return styx.Rremove(msg.tag) | |
| 187 | + else: | |
| 188 | + store.free_qid_path(msg.fid) | |
| 189 | + return styx.Rerror(msg.tag, result) | |
| 190 | + | |
| 191 | + def Tflush(self, conn, client, msg): | |
| 192 | + | |
| 193 | + store = self.clients[client] | |
| 194 | + | |
| 195 | + # If we supported a queue of commands then we would remove any pending | |
| 196 | + # commands with tags matching msg.oldtag. | |
| 197 | + | |
| 198 | + return styx.Rflush(msg.tag) | |
| 199 | + | |
| 200 | + def Twstat(self, conn, client, msg): | |
| 201 | + | |
| 202 | + store = self.clients[client] | |
| 203 | + | |
| 204 | + s = store.wstat(msg.fid, msg.stat) | |
| 205 | + return styx.Rwstat(msg.tag) | |
| 206 | + | |
| 207 | + handlers = { | |
| 208 | + styx.Tversion.code: Tversion, | |
| 209 | + styx.Tattach.code: Tattach, | |
| 210 | + styx.Tstat.code: Tstat, | |
| 211 | + styx.Twalk.code: Twalk, | |
| 212 | + styx.Topen.code: Topen, | |
| 213 | + styx.Tcreate.code: Tcreate, | |
| 214 | + styx.Tread.code: Tread, | |
| 215 | + styx.Twrite.code: Twrite, | |
| 216 | + styx.Tclunk.code: Tclunk, | |
| 217 | + styx.Tremove.code: Tremove, | |
| 218 | + styx.Twstat.code: Twstat | |
| 219 | + } | |