Pure Erlang implementation of 9p2000 protocol
filesystem fs 9p2000 erlang 9p

Migrate from maps to record for internal messages encoding

hauleth.dev 6b29b56e e0dea516

verified
+335 -177
+1 -1
src/e9p.erl
··· 27 27 %-spec make_qid() 28 28 make_qid(Type, Version, Path, State) -> 29 29 #{ 30 - type => e9p_utils:qtype_from_atom(Type), 30 + type => e9p_utils:to_qtype(Type), 31 31 version => Version, 32 32 path => Path, 33 33 state => State
+12 -11
src/e9p_client.erl
··· 22 22 init({Host, Port, _Opts}) -> 23 23 {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, binary]), 24 24 case version_negotiation(Socket) of 25 - {ok, #{max_packet_size := MaxPacketSize, version := ?version}} -> 25 + {ok, #rversion{max_packet_size = MaxPacketSize, version = ?version}} -> 26 26 inet:setopts(Socket, [{active, once}]), 27 27 {ok, 28 28 #{socket => Socket, ··· 32 32 msgs => #{}, 33 33 max_packet_size => MaxPacketSize, 34 34 version => ?version}}; 35 - {ok, _, #{version := OtherVersion}} -> 35 + {ok, #rversion{version = OtherVersion}} -> 36 36 {error, {unsupported_version, OtherVersion}}; 37 37 {error, _} = Error -> 38 38 Error ··· 51 51 Id -> 52 52 Id 53 53 end, 54 - Msg = #{fid => Fid, 55 - afid => Afid, 56 - uname => Uname, 57 - aname => Aname}, 58 - e9p_transport:send(Socket, Tag, tattach, Msg), 54 + Msg = #tattach{fid = Fid, 55 + afid = Afid, 56 + uname = Uname, 57 + aname = Aname}, 58 + e9p_transport:send(Socket, Tag, Msg), 59 59 {noreply, 60 60 State#{tag := Tag + 1, 61 61 fid := Fid + 1, ··· 68 68 69 69 handle_info({tcp, Socket, Data}, #{socket := Socket} = State) -> 70 70 #{buffer := Buffer, msgs := Msgs0} = State, 71 + inet:setopts(Socket, [{active, once}]), 71 72 case e9p_transport:read_stream(<<Buffer/binary, Data/binary>>) of 72 - {ok, Tag, _Type, Msg, Rest} -> 73 + {ok, Tag, Msg, Rest} -> 73 74 Msgs = 74 75 case maps:take(Tag, Msgs0) of 75 76 {{From, _}, M} -> ··· 85 86 end. 86 87 87 88 version_negotiation(Socket) -> 88 - Msg = #{max_packet_size => ?max_packet_size, version => ?version}, 89 - e9p_transport:send(Socket, notag, tversion, Msg), 89 + Msg = #tversion{max_packet_size = ?max_packet_size, version = ?version}, 90 + e9p_transport:send(Socket, notag, Msg), 90 91 case e9p_transport:read(Socket) of 91 - {ok, _, rversion, Resp} -> 92 + {ok, _, Resp} -> 92 93 {ok, Resp}; 93 94 {error, _} = Error -> 94 95 Error
+1 -1
src/e9p_fs.erl
··· 56 56 -callback walk(QID :: e9p:qid(), unicode:chardata(), state()) -> 57 57 {e9p:qid() | false, state()}. 58 58 59 - -callback open(QID :: e9p:qid(), state()) -> result(e9p:u32()). 59 + -callback open(QID :: e9p:qid(), state()) -> result({e9p:qid(), e9p:u32()}). 60 60 61 61 -callback create(QID :: e9p:qid(), 62 62 Name :: unicode:chardata(),
+41
src/e9p_internal.hrl
··· 52 52 53 53 -define(Twstat, 126). 54 54 -define(Rwstat, 127). 55 + 56 + -record(tversion, {max_packet_size, version}). 57 + -record(rversion, {max_packet_size, version}). 58 + 59 + -record(tauth, {afid, uname, aname}). 60 + -record(rauth, {aqid}). 61 + 62 + -record(rerror, {msg}). 63 + 64 + -record(tflush, {tag}). 65 + -record(rflush, {}). 66 + 67 + -record(tattach, {fid, afid, uname, aname}). 68 + -record(rattach, {qid}). 69 + 70 + -record(twalk, {fid, new_fid, names}). 71 + -record(rwalk, {qids}). 72 + 73 + -record(topen, {fid, mode}). 74 + -record(ropen, {qid, io_unit}). 75 + 76 + -record(tcreate, {fid, name, perm, mode}). 77 + -record(rcreate, {qid, io_unit}). 78 + 79 + -record(tread, {fid, offset, len}). 80 + -record(rread, {data}). 81 + 82 + -record(twrite, {fid, offset, data}). 83 + -record(rwrite, {len}). 84 + 85 + -record(tclunk, {fid}). 86 + -record(rclunk, {}). 87 + 88 + -record(tremove, {fid}). 89 + -record(rremove, {}). 90 + 91 + -record(tstat, {fid}). 92 + -record(rstat, {stat}). 93 + 94 + -record(twstat, {fid, stat}). 95 + -record(rwstat, {}).
+96 -95
src/e9p_msg.erl
··· 6 6 %% @end 7 7 -module(e9p_msg). 8 8 9 - -export([parse/1, encode/3]). 9 + -export([parse/1, encode/2, encode_stat/1]). 10 10 11 11 -export_type([tag/0, 12 - message_type/0, 13 - request_message_type/0, 14 - response_message_type/0 12 + message/0, 13 + request_message/0, 14 + response_message/0 15 15 ]). 16 16 17 17 -include("e9p_internal.hrl"). 18 18 19 19 -type tag() :: 16#0000..16#FFFF. 20 20 21 - -type request_message_type() :: 22 - tversion | 23 - tauth | 24 - tattach | 25 - tflush | 26 - twalk | 27 - topen | 28 - tcreate | 29 - tread | 30 - twrite | 31 - tclunk | 32 - tremove | 33 - tstat | 34 - twstat. 21 + -type request_message() :: 22 + #tversion{} | 23 + #tauth{} | 24 + #tattach{} | 25 + #tflush{} | 26 + #twalk{} | 27 + #topen{} | 28 + #tcreate{} | 29 + #tread{} | 30 + #twrite{} | 31 + #tclunk{} | 32 + #tremove{} | 33 + #tstat{} | 34 + #twstat{}. 35 35 36 - -type response_message_type() :: 37 - rversion | 38 - rauth | 39 - rattach | 40 - rerror | 41 - rflush | 42 - rwalk | 43 - ropen | 44 - rcreate | 45 - rread | 46 - rwrite | 47 - rclunk | 48 - rstat | 49 - rwstat. 36 + -type response_message() :: 37 + #rversion{} | 38 + #rauth{} | 39 + #rattach{} | 40 + #rerror{} | 41 + #rflush{} | 42 + #rwalk{} | 43 + #ropen{} | 44 + #rcreate{} | 45 + #rread{} | 46 + #rwrite{} | 47 + #rclunk{} | 48 + #rstat{} | 49 + #rwstat{}. 50 50 51 - -type message_type() :: request_message_type() | response_message_type(). 51 + -type message() :: request_message() | response_message(). 52 52 53 + -spec parse(binary()) -> {ok, tag(), message()} | {error, term()}. 53 54 parse(<<Type:1/?int, Tag:2/?int, Data/binary>>) -> 54 55 case do_parse(Type, Data) of 55 - {ok, T, Parsed} -> 56 - {ok, Tag, T, Parsed}; 56 + {ok, Parsed} -> 57 + {ok, Tag, Parsed}; 57 58 {error, Reason} -> 58 59 {error, Reason} 59 60 end. 60 61 61 62 %% version - negotiate protocol version 62 63 do_parse(?Tversion, <<MSize:4/?int, VSize:?len, Version:VSize/binary>>) -> 63 - {ok, tversion, #{max_packet_size => MSize, version => Version}}; 64 + {ok, #tversion{max_packet_size = MSize, version = Version}}; 64 65 do_parse(?Rversion, <<MSize:4/?int, VSize:?len, Version:VSize/binary>>) -> 65 - {ok, rversion, #{max_packet_size => MSize, version => Version}}; 66 + {ok, #rversion{max_packet_size = MSize, version = Version}}; 66 67 67 68 %% attach, auth - messages to establish a connection 68 69 do_parse(?Tauth, <<AFID:4/?int, 69 70 UnameLen:?len, Uname:UnameLen/binary, 70 71 AnameLen:?len, Aname:AnameLen/binary>>) -> 71 - {ok, tauth, #{afid => AFID, 72 - uname => Uname, 73 - aname => Aname}}; 72 + {ok, #tauth{afid = AFID, 73 + uname = Uname, 74 + aname = Aname}}; 74 75 do_parse(?Rauth, <<AQID:13/binary>>) -> 75 - {ok, rauth, #{aqid => binary_to_qid(AQID)}}; 76 + {ok, #rauth{aqid = binary_to_qid(AQID)}}; 76 77 77 78 do_parse(?Tattach, <<FID:4/?int, 78 79 AFID:4/?int, 79 80 ULen:?len, Uname:ULen/binary, 80 81 ALen:?len, Aname:ALen/binary>>) -> 81 - {ok, tattach, #{fid => FID, 82 - afid => AFID, 83 - uname => Uname, 84 - aname => Aname}}; 82 + {ok, #tattach{fid = FID, 83 + afid = AFID, 84 + uname = Uname, 85 + aname = Aname}}; 85 86 do_parse(?Rattach, <<QID:13/binary>>) -> 86 - {ok, rattach, #{qid => binary_to_qid(QID)}}; 87 + {ok, #rattach{qid = binary_to_qid(QID)}}; 87 88 88 89 %% clunk - forget about a fid 89 90 do_parse(?Tclunk, <<FID:4/?int>>) -> 90 - {ok, tclunk, #{fid => FID}}; 91 + {ok, #tclunk{fid = FID}}; 91 92 do_parse(?Rclunk, <<>>) -> 92 - {ok, rclunk, #{}}; 93 + {ok, #rclunk{}}; 93 94 94 95 %% error - return an error 95 96 do_parse(?Rerror, <<ELen:?len, Error:ELen/binary>>) -> 96 - {ok, rerror, #{error => Error}}; 97 + {ok, #rerror{msg = Error}}; 97 98 98 99 %% flush - abort a message 99 100 do_parse(?Tflush, <<Tag:2/?int>>) -> 100 - {ok, tflush, #{tag => Tag}}; 101 + {ok, #tflush{tag = Tag}}; 101 102 do_parse(?Rflush, <<>>) -> 102 - {ok, rflush, #{}}; 103 + {ok, #rflush{}}; 103 104 104 105 %% open, create - prepare a fid for I/O on an existing or new file 105 106 do_parse(?Topen, <<FID:4/?int, Mode:1/?int>>) -> 106 - {ok, topen, #{fid => FID, mode => Mode}}; 107 + {ok, #topen{fid = FID, mode = Mode}}; 107 108 do_parse(?Ropen, <<QID:13/binary, IOUnit:4/?int>>) -> 108 - {ok, ropen, #{qid => binary_to_qid(QID), io_unit => IOUnit}}; 109 + {ok, #ropen{qid = binary_to_qid(QID), io_unit = IOUnit}}; 109 110 110 111 do_parse(?Tcreate, <<FID:4/?int, 111 112 NLen:?len, Name:NLen/binary, 112 113 Perm:4/?int, 113 114 Mode:1/?int>>) -> 114 - {ok, tcreate, #{fid => FID, name => Name, perm => Perm, mode => Mode}}; 115 + {ok, #tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}}; 115 116 do_parse(?Rcreate, <<QID:13/binary, IOUnit:4/?int>>) -> 116 - {ok, rcreate, #{qid => binary_to_qid(QID), io_unit => IOUnit}}; 117 + {ok, #rcreate{qid = binary_to_qid(QID), io_unit = IOUnit}}; 117 118 118 119 %% remove - remove a file from a server 119 120 do_parse(?Tremove, <<FID:4/?int>>) -> 120 - {ok, tremove, #{fid => FID}}; 121 + {ok, #tremove{fid = FID}}; 121 122 do_parse(?Rremove, <<>>) -> 122 - {ok, rremove, #{}}; 123 + {ok, #rremove{}}; 123 124 124 125 %% stat, wstat - inquire or change file attributes 125 126 do_parse(?Tstat, <<FID:4/?int>>) -> 126 - {ok, tstat, #{fid => FID}}; 127 + {ok, #tstat{fid = FID}}; 127 128 do_parse(?Rstat, <<DLen:?len, Data:DLen/binary>>) -> 128 129 case parse_stat(Data) of 129 130 {ok, Stat} -> 130 - {ok, rstat, #{stat => Stat}}; 131 + {ok, #rstat{stat = Stat}}; 131 132 132 133 {error, _} = Error -> 133 134 Error ··· 136 137 do_parse(?Twstat, <<FID:4/?int, DLen:?len, Data:DLen/binary>>) -> 137 138 case parse_stat(Data) of 138 139 {ok, Stat} -> 139 - {ok, twstat, #{fid => FID, stat => Stat}}; 140 + {ok, #twstat{fid = FID, stat = Stat}}; 140 141 141 142 {error, _} = Error -> 142 143 Error 143 144 end; 144 145 do_parse(?Rwstat, <<>>) -> 145 - {ok, rwstat, #{}}; 146 + {ok, #rwstat{}}; 146 147 147 148 %% walk - descend a directory hierarchy 148 149 do_parse(?Twalk, <<FID:4/?int, NewFID:4/?int, NWNLen:?len, Rest/binary>>) -> ··· 150 151 Len = length(NWNames), 151 152 if 152 153 Len == NWNLen -> 153 - {ok, twalk, #{fid => FID, new_fid => NewFID, names => NWNames}}; 154 + {ok, #twalk{fid = FID, new_fid = NewFID, names = NWNames}}; 154 155 true -> 155 156 {error, {invalid_walk_length, NWNLen, Len}} 156 157 end; 157 158 do_parse(?Rwalk, <<NWQLen:?len, QIDs:(NWQLen * 13)/binary>>) -> 158 - {ok, rwalk, #{qids => [binary_to_qid(QID) || <<QID:13/binary>> <= QIDs]}}; 159 + {ok, #rwalk{qids = [binary_to_qid(QID) || <<QID:13/binary>> <= QIDs]}}; 159 160 160 - do_parse(?Tread, <<FID:4/?int, Offset:8/?int, Count:4/?int>>) -> 161 - {ok, tread, #{fid => FID, offset => Offset, count => Count}}; 161 + do_parse(?Tread, <<FID:4/?int, Offset:8/?int, Len:4/?int>>) -> 162 + {ok, #tread{fid = FID, offset = Offset, len = Len}}; 162 163 do_parse(?Rread, <<Count:4/?int, Data:Count/?int>>) -> 163 - {ok, rread, #{data => Data}}; 164 + {ok, #rread{data = Data}}; 164 165 165 166 do_parse(Type, Data) -> 166 167 {error, {invalid_message, Type, Data}}. ··· 193 194 }}; 194 195 parse_stat(_) -> {error, invalid_stat_data}. 195 196 196 - -spec encode(Tag :: tag() | notag, Type :: message_type(), Data :: map()) -> iodata(). 197 - encode(Tag, Type, Data) -> 198 - {MT, Encoded} = do_encode(Type, Data), 197 + -spec encode(Tag :: tag() | notag, Data :: message()) -> iodata(). 198 + encode(Tag, Data) -> 199 + {MT, Encoded} = do_encode(Data), 199 200 Tag0 = case Tag of 200 201 notag -> ?notag; 201 202 V -> V 202 203 end, 203 204 [<<MT:1/?int, Tag0:2/?int>> | Encoded]. 204 205 205 - do_encode(tversion, #{max_packet_size := MSize, version := Version}) -> 206 + do_encode(#tversion{max_packet_size = MSize, version = Version}) -> 206 207 {?Tversion, [<<MSize:4/?int>> | encode_str(Version)]}; 207 - do_encode(rversion, #{max_packet_size := MSize, version := Version}) -> 208 + do_encode(#rversion{max_packet_size = MSize, version = Version}) -> 208 209 {?Rversion, [<<MSize:4/?int>> | encode_str(Version)]}; 209 210 210 - do_encode(tauth, #{afid := AFID, uname := Uname, aname := Aname}) -> 211 + do_encode(#tauth{afid = AFID, uname = Uname, aname = Aname}) -> 211 212 {?Tauth, [<<AFID:4/?int>>, encode_str(Uname), encode_str(Aname)]}; 212 - do_encode(rauth, #{aqid := AQID}) -> 213 + do_encode(#rauth{aqid = AQID}) -> 213 214 {?Rauth, qid_to_binary(AQID)}; 214 215 215 - do_encode(tattach, #{fid := FID, afid := AFID, uname := Uname, aname := Aname}) -> 216 + do_encode(#tattach{fid = FID, afid = AFID, uname = Uname, aname = Aname}) -> 216 217 {?Tattach, [<<FID:4/?int, AFID:4/?int>>, encode_str(Uname), encode_str(Aname)]}; 217 - do_encode(rattach, #{qid := QID}) -> 218 + do_encode(#rattach{qid = QID}) -> 218 219 {?Rattach, qid_to_binary(QID)}; 219 220 220 - do_encode(tclunk, #{fid := FID}) -> 221 + do_encode(#tclunk{fid = FID}) -> 221 222 {?Tclunk, <<FID:4/?int>>}; 222 - do_encode(rclunk, _) -> 223 + do_encode(#rclunk{}) -> 223 224 {?Rclunk, []}; 224 225 225 - do_encode(rerror, #{error := Error}) -> 226 + do_encode(#rerror{msg = Error}) -> 226 227 {?Rerror, encode_str(Error)}; 227 228 228 - do_encode(tflush, #{tag := Tag}) -> 229 + do_encode(#tflush{tag = Tag}) -> 229 230 {?Tflush, <<Tag:2/?int>>}; 230 - do_encode(rflush, _) -> 231 + do_encode(#rflush{}) -> 231 232 {?Rflush, []}; 232 233 233 - do_encode(topen, #{fid := FID, mode := Mode}) -> 234 + do_encode(#topen{fid = FID, mode = Mode}) -> 234 235 {?Topen, <<FID:4/?int, Mode:1/?int>>}; 235 - do_encode(ropen, #{qid := QID, io_unit := IOUnit}) -> 236 + do_encode(#ropen{qid = QID, io_unit = IOUnit}) -> 236 237 {?Ropen, [qid_to_binary(QID), <<IOUnit:4/?int>>]}; 237 238 238 - do_encode(tcreate, #{fid := FID, name := Name, perm := Perm, mode := Mode}) -> 239 + do_encode(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}) -> 239 240 {?Tcreate, [<<FID:4/?int>>, encode_str(Name), <<Perm:4/?int, Mode:1/?int>>]}; 240 - do_encode(rcreate, #{qid := QID, io_unit := IOUnit}) -> 241 + do_encode(#rcreate{qid = QID, io_unit = IOUnit}) -> 241 242 {?Rcreate, [qid_to_binary(QID), <<IOUnit:4/?int>>]}; 242 243 243 - do_encode(tremove, #{fid := FID}) -> 244 + do_encode(#tremove{fid = FID}) -> 244 245 {?Tremove, <<FID:4/?int>>}; 245 - do_encode(rremove, _) -> 246 + do_encode(#rremove{}) -> 246 247 {?Rremove, []}; 247 248 248 - do_encode(tstat, #{fid := FID}) -> 249 + do_encode(#tstat{fid = FID}) -> 249 250 {?Tstat, <<FID:4/?int>>}; 250 - do_encode(rstat, #{stat := Stat}) -> 251 + do_encode(#rstat{stat = Stat}) -> 251 252 {?Rstat, encode_stat(Stat)}; 252 253 253 - do_encode(twstat, #{fid := FID, stat := Stat}) -> 254 + do_encode(#twstat{fid = FID, stat = Stat}) -> 254 255 {?Twstat, [<<FID:4/?int>>, encode_stat(Stat)]}; 255 - do_encode(rwstat, _) -> 256 + do_encode(#rwstat{}) -> 256 257 {?Rwstat, []}; 257 258 258 - do_encode(twalk, #{fid := FID, new_fid := NewFID, names := Names}) -> 259 + do_encode(#twalk{fid = FID, new_fid = NewFID, names = Names}) -> 259 260 ENames = [encode_str(Name) || Name <- Names], 260 261 Len = length(ENames), 261 262 {?Twalk, [<<FID:4/?int, NewFID:4/?int, Len:?len>> | ENames]}; 262 - do_encode(rwalk, #{qids := QIDs}) -> 263 + do_encode(#rwalk{qids = QIDs}) -> 263 264 EQIDs = [qid_to_binary(QID) || QID <- QIDs], 264 265 Len = length(EQIDs), 265 266 {?Rwalk, [<<Len:?len>> | EQIDs]}; 266 267 267 - do_encode(tread, #{fid := FID, offset := Offset, count := Count}) -> 268 - {?Tread, <<FID:4/?int, Offset:8/?int, Count:4/?int>>}; 269 - do_encode(rread, #{data := Data}) -> 268 + do_encode(#tread{fid = FID, offset = Offset, len = Len}) -> 269 + {?Tread, <<FID:4/?int, Offset:8/?int, Len:4/?int>>}; 270 + do_encode(#rread{data = Data}) -> 270 271 {?Rread, encode_str(Data)}. 271 272 272 273 encode_stat(#{
+101 -43
src/e9p_server.erl
··· 4 4 5 5 -module(e9p_server). 6 6 7 + -include("e9p_internal.hrl"). 8 + 7 9 -include_lib("kernel/include/logger.hrl"). 8 10 9 11 -export([start_link/2, ··· 26 28 accept_loop(LSock, Handler) -> 27 29 case gen_tcp:accept(LSock, 5000) of 28 30 {ok, Sock} -> 31 + % TODO: Handle connected clients in separate process 29 32 ok = ?MODULE:loop(Sock, #{}, Handler), 30 33 ?MODULE:accept_loop(LSock, Handler); 31 34 {error, timeout} -> ··· 36 39 37 40 loop(Sock, FIDs, Handler) -> 38 41 case e9p_transport:read(Sock) of 39 - {ok, Tag, Type, Data} -> 40 - case handle_message(Type, Data, FIDs, Handler) of 41 - {ok, {RType, RData}, RFIDs, RHandler} -> 42 - e9p_transport:send(Sock, Tag, RType, RData), 43 - ?MODULE:loop(Sock, RFIDs, RHandler) 42 + {ok, Tag, Data} -> 43 + case handle_message(Data, FIDs, Handler) of 44 + {ok, Reply, RFIDs, RHandler} -> 45 + e9p_transport:send(Sock, Tag, Reply), 46 + ?MODULE:loop(Sock, RFIDs, RHandler); 47 + {error, Err, RHandler} -> 48 + e9p_transport:send(Sock, Tag, error_msg(Err)), 49 + ?MODULE:loop(Sock, FIDs, RHandler) 44 50 end; 45 51 {error, closed} -> 46 52 ?LOG_WARNING("Connection closed"), 47 53 ok 48 54 end. 49 55 50 - handle_message(tversion, #{version := ~"9P2000"} = Data, FIDs, Handler) -> 51 - {ok, {rversion, Data}, FIDs, Handler}; 52 - handle_message(tattach, Data, FIDs, Handler0) -> 53 - #{fid := FID, uname := _UName, aname := AName} = Data, 54 - {ok, QID, Handler} = e9p_fs:root(Handler0, AName), 55 - NFIDs = FIDs#{FID => QID}, 56 - {ok, {rattach, #{qid => QID}}, NFIDs, Handler}; 57 - handle_message(tclunk, #{fid := FID}, FIDs, Handler) -> 58 - NFIDs = maps:remove(FID, FIDs), 59 - {ok, {rflush, #{}}, NFIDs, Handler}; 60 - handle_message(twalk, Data, FIDs, Handler0) -> 61 - #{ 62 - fid := FID, 63 - new_fid := NewFID, 64 - names := Paths 65 - } = Data, 66 - #{FID := QID} = FIDs, 67 - {ok, NewQID, QIDs, Handler} = e9p_fs:walk(Handler0, QID, Paths), 68 - {ok, {rwalk, #{qids => QIDs}}, FIDs#{NewFID => NewQID}, Handler}; 69 - handle_message(topen, #{fid := FID}, FIDs, Handler0) -> 70 - #{FID := QID} = FIDs, 71 - {ok, IOUnit, Handler} = e9p_fs:open(Handler0, QID), 72 - {ok, {ropen, #{qid => QID, iounit => IOUnit}}, FIDs, Handler}; 73 - handle_message(tcreate, #{fid := FID}, FIDs, Handler0) -> 74 - #{FID := QID, name := Name, perm := Perm, mode := Mode} = FIDs, 75 - {ok, {NewQID, IOUnit}, Handler} = e9p_fs:create(Handler0, QID, Name, Perm, Mode), 76 - {ok, {rcreate, #{qid => NewQID, iounit => IOUnit}}, FIDs, Handler}; 77 - handle_message(tread, Data, FIDs, Handler0) -> 78 - #{ 79 - fid := FID, 80 - offset := Offset, 81 - count := Count 82 - } = Data, 83 - #{FID := QID} = FIDs, 84 - {ok, Data, Handler} = e9p_fs:read(Handler0, QID, Offset, Count), 85 - {ok, {rread, #{data => Data}}, FIDs, Handler}; 86 - handle_message(_Type, _Data, FIDs, Handler) -> 87 - {ok, {rerror, #{error => ~"Unknown request type"}}, FIDs, Handler}. 56 + handle_message(#tversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler) -> 57 + % Currently only "basic" 9p2000 version is supported, without any extensions 58 + % like `.u` or `.L` 59 + {ok, #rversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler}; 60 + 61 + handle_message(#tflush{}, FIDs, Handler) -> 62 + % Currently there is no support for parallel messages, so this does simply 63 + % nothing 64 + {ok, #rflush{}, FIDs, Handler}; 65 + 66 + handle_message(#tattach{fid = FID, uname = _UName, aname = AName}, FIDs, Handler0) -> 67 + maybe 68 + {ok, QID, Handler} ?= e9p_fs:root(Handler0, AName), 69 + NFIDs = FIDs#{FID => QID}, 70 + {ok, #rattach{qid = QID}, NFIDs, Handler} 71 + end; 72 + 73 + handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) -> 74 + maybe 75 + {ok, QID} ?= get_qid(FIDs, FID), 76 + {ok, NewQID, QIDs, Handler} ?= e9p_fs:walk(Handler0, QID, Paths), 77 + {ok, #rwalk{qids = QIDs}, FIDs#{NewFID => NewQID}, Handler} 78 + end; 79 + 80 + handle_message(#topen{fid = FID}, FIDs, Handler0) -> 81 + maybe 82 + {ok, QID} ?= get_qid(FIDs, FID), 83 + {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID), 84 + {ok, #ropen{qid = QID, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} 85 + end; 86 + handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) -> 87 + maybe 88 + {ok, QID} ?= get_qid(FIDs, FID), 89 + {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:create(Handler0, QID, Name, Perm, Mode), 90 + {ok, #rcreate{qid = NewQID, io_unit = IOUnit}, FIDs, Handler} 91 + end; 92 + 93 + handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> 94 + maybe 95 + {ok, QID} ?= get_qid(FIDs, FID), 96 + {ok, Data, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len), 97 + {ok, #rread{data = Data}, FIDs, Handler} 98 + end; 99 + handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) -> 100 + maybe 101 + {ok, QID} ?= get_qid(FIDs, FID), 102 + {ok, Data, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), 103 + {ok, #rread{data = Data}, FIDs, Handler} 104 + end; 105 + 106 + handle_message(#tclunk{fid = FID}, FIDs, Handler0) -> 107 + maybe 108 + {ok, QID} ?= get_qid(FIDs, FID), 109 + {ok, Handler} ?= e9p_fs:clunk(Handler0, QID), 110 + NFIDs = maps:remove(FID, FIDs), 111 + {ok, #rclunk{}, NFIDs, Handler} 112 + end; 113 + 114 + handle_message(#tremove{fid = FID}, FIDs, Handler0) -> 115 + maybe 116 + {ok, QID} ?= get_qid(FIDs, FID), 117 + {ok, Handler} ?= e9p_fs:remove(Handler0, QID), 118 + {ok, #rremove{}, FIDs, Handler} 119 + end; 120 + 121 + handle_message(#tstat{fid = FID}, FIDs, Handler0) -> 122 + maybe 123 + {ok, QID} ?= get_qid(FIDs, FID), 124 + {ok, Stat, Handler} ?= e9p_fs:stat(Handler0, QID), 125 + {ok, #rstat{stat = Stat}, FIDs, Handler} 126 + end; 127 + 128 + handle_message(#twstat{fid = FID, stat = Stat}, FIDs, Handler0) -> 129 + maybe 130 + {ok, QID} ?= get_qid(FIDs, FID), 131 + {ok, Handler} ?= e9p_fs:wstat(Handler0, QID, Stat), 132 + {ok, #rwstat{}, FIDs, Handler} 133 + end; 134 + 135 + handle_message(_Msg, _FIDs, Handler) -> 136 + {error, ~"Unknown request type", Handler}. 137 + 138 + get_qid(FIDs, FID) -> 139 + case FIDs of 140 + #{FID := QID} -> {ok, QID}; 141 + _ -> {error, io_lib:fwrite(~"Unknown FID: ~B", [FID])} 142 + end. 143 + 144 + error_msg(Data) -> 145 + #rerror{msg = io_lib:fwrite(~"~p", [Data])}.
+7 -7
src/e9p_transport.erl
··· 6 6 7 7 -include("e9p_internal.hrl"). 8 8 9 - -export([send/4, read/1, read_stream/1]). 9 + -export([send/3, read/1, read_stream/1]). 10 10 11 - send(Socket, Tag, Type, Message) -> 12 - Encoded = e9p_msg:encode(Tag, Type, Message), 11 + send(Socket, Tag, Message) -> 12 + Encoded = e9p_msg:encode(Tag, Message), 13 13 Size = iolist_size(Encoded) + 4, 14 14 gen_tcp:send(Socket, [<<Size:4/?int>>, Encoded]). 15 15 ··· 19 19 case gen_tcp:recv(Socket, Size - 4) of 20 20 {ok, Data} when is_binary(Data) -> 21 21 case e9p_msg:parse(Data) of 22 - {ok, Tag, Type, Msg} -> 23 - {ok, Tag, Type, Msg}; 22 + {ok, Tag, Msg} -> 23 + {ok, Tag, Msg}; 24 24 {error, _} = Error -> 25 25 Error 26 26 end; ··· 33 33 34 34 read_stream(<<Size:4/?int, Data:(Size - 4)/binary, Rest/binary>> = Input) -> 35 35 case e9p_msg:parse(Data) of 36 - {ok, Tag, Type, Msg} -> 37 - {ok, Tag, Type, Msg, Rest}; 36 + {ok, Tag, Msg} -> 37 + {ok, Tag, Msg, Rest}; 38 38 {error, Error} -> 39 39 {error, Error, Input} 40 40 end;
+33 -5
src/e9p_unfs.erl
··· 8 8 9 9 -include_lib("kernel/include/file.hrl"). 10 10 11 - -export([init/1, root/2, walk/3, stat/2, read/4, write/4]). 11 + -export([init/1, root/2, walk/3, stat/2, open/2, read/4]). 12 12 13 13 init(#{path := Path}) -> 14 14 {ok, #{root => Path}}. ··· 21 21 Next = filename:join(Path, File), 22 22 case file:read_file_info(Next, [{time, posix}]) of 23 23 {ok, #file_info{type = Type, inode = Inode}} -> 24 - NQid = e9p:make_qid(Type, 0, Inode, Next), 24 + NQid = e9p:make_qid(Type, 0, Inode, {Next, []}), 25 25 {NQid, State}; 26 26 27 27 {error, _} -> 28 28 {false, State} 29 29 end. 30 30 31 - stat(_Qid, State) -> 31 + stat(#{state := {Path, []}} = QID, State) -> 32 + case file:read_file_info(Path, [{time, posix}]) of 33 + {ok, FileInfo} -> 34 + Stat = file_info_to_stat(QID, FileInfo), 35 + {ok, Stat, State}; 36 + {error, Error} -> 37 + {error, Error, State} 38 + end. 39 + 40 + open(_Qid, State) -> 32 41 {error, unimplemented, State}. 33 42 34 43 read(_Qid, _Offset, _Len, State) -> 35 44 {error, unimplemented, State}. 36 45 37 - write(_Qid, _Offset, _Data, State) -> 38 - {error, unimplemented, State}. 46 + file_info_to_stat( 47 + #{state := Path} = QID, 48 + #file_info{ 49 + size = Len, 50 + atime = Atime, 51 + mtime = Mtime, 52 + mode = Mode 53 + }) -> 54 + #{ 55 + type => 0, 56 + dev => 0, 57 + qid => QID, 58 + mode => Mode, 59 + atime => Atime, 60 + mtime => Mtime, 61 + length => Len, 62 + name => filename:basename(Path), 63 + uid => ~"", 64 + gid => ~"", 65 + muid => ~"" 66 + }.
+16 -9
src/e9p_utils.erl
··· 4 4 5 5 -module(e9p_utils). 6 6 7 - -export([normalize_path/1, qtype_from_atom/1]). 7 + -export([normalize_path/1, to_qtype/1]). 8 8 9 9 normalize_path(List) -> normalize_path(List, []). 10 10 ··· 16 16 normalize_path([P | Rest], Acc) -> 17 17 normalize_path(Rest, [P | Acc]). 18 18 19 - qtype_from_atom(dir) -> 16#80; 20 - qtype_from_atom(append) -> 16#40; 21 - qtype_from_atom(excl) -> 16#20; 22 - qtype_from_atom(device) -> 16#10; 23 - qtype_from_atom(auth) -> 16#08; 24 - qtype_from_atom(tmp) -> 16#04; 25 - qtype_from_atom(symlink) -> 16#02; 26 - qtype_from_atom(regular) -> 16#00. 19 + to_qtype(List) when is_list(List) -> 20 + lists:foldl( 21 + fun(El, Acc) when is_integer(Acc) -> to_qtype(El) bor Acc end, 22 + 0, 23 + List 24 + ); 25 + 26 + to_qtype(dir) -> 16#80; 27 + to_qtype(append) -> 16#40; 28 + to_qtype(excl) -> 16#20; 29 + to_qtype(device) -> 16#10; 30 + to_qtype(auth) -> 16#08; 31 + to_qtype(tmp) -> 16#04; 32 + to_qtype(symlink) -> 16#02; 33 + to_qtype(regular) -> 16#00.
+27 -5
test/prop_e9p_msg.erl
··· 4 4 5 5 -module(prop_e9p_msg). 6 6 7 + -include("e9p_internal.hrl"). 7 8 -include_lib("proper/include/proper.hrl"). 8 9 % -include_lib("stdlib/include/assert.hrl"). 9 10 11 + afid() -> integer(16#0000, 16#FFFF). 12 + 13 + prop_can_decode_encoded_tversion() -> 14 + ?FORALL({Version, MPS}, {binary(), afid()}, 15 + begin 16 + enc_dec(#tversion{version = Version, max_packet_size = MPS}) 17 + end). 18 + 19 + prop_can_decode_encoded_rversion() -> 20 + ?FORALL({Version, MPS}, {binary(), afid()}, 21 + begin 22 + enc_dec(#rversion{version = Version, max_packet_size = MPS}) 23 + end). 24 + 10 25 prop_can_decode_encoded_tauth() -> 11 - ?FORALL({Uname, Aname}, {binary(), binary()}, 26 + ?FORALL({Afid, Uname, Aname}, {integer(0, 16#FFFF), binary(), binary()}, 12 27 begin 13 - enc_dec(tauth, #{afid => 1, uname => Uname, aname => Aname}) 28 + enc_dec(#tauth{afid = Afid, uname = Uname, aname = Aname}) 14 29 end). 15 30 16 - enc_dec(Kind, Data) -> 31 + prop_can_decode_encoded_rerror() -> 32 + ?FORALL({Msg}, {binary()}, 33 + begin 34 + enc_dec(#rerror{msg = Msg}) 35 + end). 36 + 37 + 38 + enc_dec(Data) -> 17 39 Tag = 1, 18 - Out = e9p_msg:encode(Tag, Kind, Data), 40 + Out = e9p_msg:encode(Tag, Data), 19 41 Encoded = iolist_to_binary(Out), 20 - {ok, Tag, Kind, Data} =:= e9p_msg:parse(Encoded). 42 + {ok, Tag, Data} =:= e9p_msg:parse(Encoded).