History

Separated the server code from the host filing system code, creating specific modules for each.

David Boddie committed 8 years ago

Commit 1ed27611 parent b6957db2 files · +223 −206Browse files

Unified · Split

2 files
  1. styxfs.py +4 −206
  2. styxserver.py +219 −0
styxfs.py+4−206
@@ -17,211 +17,8 @@ · 16 unmodified lines
17# You should have received a copy of the GNU General Public License17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20import os, shutil, socket, stat, sys20import os, stat, sys
21import styx21import styx, styxserver
22
23class StyxFSError(Exception):
24 pass
25
26class 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
225 22
226class FileStore:23class FileStore:
227 24
@@ -462,5 +259,6 @@ if __name__ == "__main__": · 234 unmodified lines
462 directory = sys.argv[1]259 directory = sys.argv[1]
463 port = int(sys.argv[2])260 port = int(sys.argv[2])
464 261
465 server = StyxFSServer(directory)262 store = FileStore(directory)
263 server = styxserver.StyxServer(store)
466 server.serve("", port)264 server.serve("", port)
styxserver.py+219−0
@@ -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
18import socket
19import styx
20
21class StyxServerError(Exception):
22 pass
23
24class 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 }