diff --git a/src/e9p.erl b/src/e9p.erl index e16421e..9035900 100644 --- a/src/e9p.erl +++ b/src/e9p.erl @@ -27,7 +27,7 @@ %-spec make_qid() make_qid(Type, Version, Path, State) -> #{ - type => e9p_utils:qtype_from_atom(Type), + type => e9p_utils:to_qtype(Type), version => Version, path => Path, state => State diff --git a/src/e9p_client.erl b/src/e9p_client.erl index 2157aaf..2bb8447 100644 --- a/src/e9p_client.erl +++ b/src/e9p_client.erl @@ -22,7 +22,7 @@ start_link(Host, Port, Opts) -> init({Host, Port, _Opts}) -> {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, binary]), case version_negotiation(Socket) of - {ok, #{max_packet_size := MaxPacketSize, version := ?version}} -> + {ok, #rversion{max_packet_size = MaxPacketSize, version = ?version}} -> inet:setopts(Socket, [{active, once}]), {ok, #{socket => Socket, @@ -32,7 +32,7 @@ init({Host, Port, _Opts}) -> msgs => #{}, max_packet_size => MaxPacketSize, version => ?version}}; - {ok, _, #{version := OtherVersion}} -> + {ok, #rversion{version = OtherVersion}} -> {error, {unsupported_version, OtherVersion}}; {error, _} = Error -> Error @@ -51,11 +51,11 @@ handle_call({attach, Auth, Uname, Aname}, From, State) -> Id -> Id end, - Msg = #{fid => Fid, - afid => Afid, - uname => Uname, - aname => Aname}, - e9p_transport:send(Socket, Tag, tattach, Msg), + Msg = #tattach{fid = Fid, + afid = Afid, + uname = Uname, + aname = Aname}, + e9p_transport:send(Socket, Tag, Msg), {noreply, State#{tag := Tag + 1, fid := Fid + 1, @@ -68,8 +68,9 @@ handle_cast(_Msg, State) -> handle_info({tcp, Socket, Data}, #{socket := Socket} = State) -> #{buffer := Buffer, msgs := Msgs0} = State, + inet:setopts(Socket, [{active, once}]), case e9p_transport:read_stream(<>) of - {ok, Tag, _Type, Msg, Rest} -> + {ok, Tag, Msg, Rest} -> Msgs = case maps:take(Tag, Msgs0) of {{From, _}, M} -> @@ -85,10 +86,10 @@ handle_info({tcp, Socket, Data}, #{socket := Socket} = State) -> end. version_negotiation(Socket) -> - Msg = #{max_packet_size => ?max_packet_size, version => ?version}, - e9p_transport:send(Socket, notag, tversion, Msg), + Msg = #tversion{max_packet_size = ?max_packet_size, version = ?version}, + e9p_transport:send(Socket, notag, Msg), case e9p_transport:read(Socket) of - {ok, _, rversion, Resp} -> + {ok, _, Resp} -> {ok, Resp}; {error, _} = Error -> Error diff --git a/src/e9p_fs.erl b/src/e9p_fs.erl index 810b42b..f6c89a1 100644 --- a/src/e9p_fs.erl +++ b/src/e9p_fs.erl @@ -56,7 +56,7 @@ -callback walk(QID :: e9p:qid(), unicode:chardata(), state()) -> {e9p:qid() | false, state()}. --callback open(QID :: e9p:qid(), state()) -> result(e9p:u32()). +-callback open(QID :: e9p:qid(), state()) -> result({e9p:qid(), e9p:u32()}). -callback create(QID :: e9p:qid(), Name :: unicode:chardata(), diff --git a/src/e9p_internal.hrl b/src/e9p_internal.hrl index 4ff71e3..fab19e5 100644 --- a/src/e9p_internal.hrl +++ b/src/e9p_internal.hrl @@ -52,3 +52,44 @@ -define(Twstat, 126). -define(Rwstat, 127). + +-record(tversion, {max_packet_size, version}). +-record(rversion, {max_packet_size, version}). + +-record(tauth, {afid, uname, aname}). +-record(rauth, {aqid}). + +-record(rerror, {msg}). + +-record(tflush, {tag}). +-record(rflush, {}). + +-record(tattach, {fid, afid, uname, aname}). +-record(rattach, {qid}). + +-record(twalk, {fid, new_fid, names}). +-record(rwalk, {qids}). + +-record(topen, {fid, mode}). +-record(ropen, {qid, io_unit}). + +-record(tcreate, {fid, name, perm, mode}). +-record(rcreate, {qid, io_unit}). + +-record(tread, {fid, offset, len}). +-record(rread, {data}). + +-record(twrite, {fid, offset, data}). +-record(rwrite, {len}). + +-record(tclunk, {fid}). +-record(rclunk, {}). + +-record(tremove, {fid}). +-record(rremove, {}). + +-record(tstat, {fid}). +-record(rstat, {stat}). + +-record(twstat, {fid, stat}). +-record(rwstat, {}). diff --git a/src/e9p_msg.erl b/src/e9p_msg.erl index cf9dd6a..d52ad7d 100644 --- a/src/e9p_msg.erl +++ b/src/e9p_msg.erl @@ -6,128 +6,129 @@ %% @end -module(e9p_msg). --export([parse/1, encode/3]). +-export([parse/1, encode/2, encode_stat/1]). -export_type([tag/0, - message_type/0, - request_message_type/0, - response_message_type/0 + message/0, + request_message/0, + response_message/0 ]). -include("e9p_internal.hrl"). -type tag() :: 16#0000..16#FFFF. --type request_message_type() :: - tversion | - tauth | - tattach | - tflush | - twalk | - topen | - tcreate | - tread | - twrite | - tclunk | - tremove | - tstat | - twstat. - --type response_message_type() :: - rversion | - rauth | - rattach | - rerror | - rflush | - rwalk | - ropen | - rcreate | - rread | - rwrite | - rclunk | - rstat | - rwstat. - --type message_type() :: request_message_type() | response_message_type(). - +-type request_message() :: + #tversion{} | + #tauth{} | + #tattach{} | + #tflush{} | + #twalk{} | + #topen{} | + #tcreate{} | + #tread{} | + #twrite{} | + #tclunk{} | + #tremove{} | + #tstat{} | + #twstat{}. + +-type response_message() :: + #rversion{} | + #rauth{} | + #rattach{} | + #rerror{} | + #rflush{} | + #rwalk{} | + #ropen{} | + #rcreate{} | + #rread{} | + #rwrite{} | + #rclunk{} | + #rstat{} | + #rwstat{}. + +-type message() :: request_message() | response_message(). + +-spec parse(binary()) -> {ok, tag(), message()} | {error, term()}. parse(<>) -> case do_parse(Type, Data) of - {ok, T, Parsed} -> - {ok, Tag, T, Parsed}; + {ok, Parsed} -> + {ok, Tag, Parsed}; {error, Reason} -> {error, Reason} end. %% version - negotiate protocol version do_parse(?Tversion, <>) -> - {ok, tversion, #{max_packet_size => MSize, version => Version}}; + {ok, #tversion{max_packet_size = MSize, version = Version}}; do_parse(?Rversion, <>) -> - {ok, rversion, #{max_packet_size => MSize, version => Version}}; + {ok, #rversion{max_packet_size = MSize, version = Version}}; %% attach, auth - messages to establish a connection do_parse(?Tauth, <>) -> - {ok, tauth, #{afid => AFID, - uname => Uname, - aname => Aname}}; + {ok, #tauth{afid = AFID, + uname = Uname, + aname = Aname}}; do_parse(?Rauth, <>) -> - {ok, rauth, #{aqid => binary_to_qid(AQID)}}; + {ok, #rauth{aqid = binary_to_qid(AQID)}}; do_parse(?Tattach, <>) -> - {ok, tattach, #{fid => FID, - afid => AFID, - uname => Uname, - aname => Aname}}; + {ok, #tattach{fid = FID, + afid = AFID, + uname = Uname, + aname = Aname}}; do_parse(?Rattach, <>) -> - {ok, rattach, #{qid => binary_to_qid(QID)}}; + {ok, #rattach{qid = binary_to_qid(QID)}}; %% clunk - forget about a fid do_parse(?Tclunk, <>) -> - {ok, tclunk, #{fid => FID}}; + {ok, #tclunk{fid = FID}}; do_parse(?Rclunk, <<>>) -> - {ok, rclunk, #{}}; + {ok, #rclunk{}}; %% error - return an error do_parse(?Rerror, <>) -> - {ok, rerror, #{error => Error}}; + {ok, #rerror{msg = Error}}; %% flush - abort a message do_parse(?Tflush, <>) -> - {ok, tflush, #{tag => Tag}}; + {ok, #tflush{tag = Tag}}; do_parse(?Rflush, <<>>) -> - {ok, rflush, #{}}; + {ok, #rflush{}}; %% open, create - prepare a fid for I/O on an existing or new file do_parse(?Topen, <>) -> - {ok, topen, #{fid => FID, mode => Mode}}; + {ok, #topen{fid = FID, mode = Mode}}; do_parse(?Ropen, <>) -> - {ok, ropen, #{qid => binary_to_qid(QID), io_unit => IOUnit}}; + {ok, #ropen{qid = binary_to_qid(QID), io_unit = IOUnit}}; do_parse(?Tcreate, <>) -> - {ok, tcreate, #{fid => FID, name => Name, perm => Perm, mode => Mode}}; + {ok, #tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}}; do_parse(?Rcreate, <>) -> - {ok, rcreate, #{qid => binary_to_qid(QID), io_unit => IOUnit}}; + {ok, #rcreate{qid = binary_to_qid(QID), io_unit = IOUnit}}; %% remove - remove a file from a server do_parse(?Tremove, <>) -> - {ok, tremove, #{fid => FID}}; + {ok, #tremove{fid = FID}}; do_parse(?Rremove, <<>>) -> - {ok, rremove, #{}}; + {ok, #rremove{}}; %% stat, wstat - inquire or change file attributes do_parse(?Tstat, <>) -> - {ok, tstat, #{fid => FID}}; + {ok, #tstat{fid = FID}}; do_parse(?Rstat, <>) -> case parse_stat(Data) of {ok, Stat} -> - {ok, rstat, #{stat => Stat}}; + {ok, #rstat{stat = Stat}}; {error, _} = Error -> Error @@ -136,13 +137,13 @@ do_parse(?Rstat, <>) -> do_parse(?Twstat, <>) -> case parse_stat(Data) of {ok, Stat} -> - {ok, twstat, #{fid => FID, stat => Stat}}; + {ok, #twstat{fid = FID, stat = Stat}}; {error, _} = Error -> Error end; do_parse(?Rwstat, <<>>) -> - {ok, rwstat, #{}}; + {ok, #rwstat{}}; %% walk - descend a directory hierarchy do_parse(?Twalk, <>) -> @@ -150,17 +151,17 @@ do_parse(?Twalk, <>) -> Len = length(NWNames), if Len == NWNLen -> - {ok, twalk, #{fid => FID, new_fid => NewFID, names => NWNames}}; + {ok, #twalk{fid = FID, new_fid = NewFID, names = NWNames}}; true -> {error, {invalid_walk_length, NWNLen, Len}} end; do_parse(?Rwalk, <>) -> - {ok, rwalk, #{qids => [binary_to_qid(QID) || <> <= QIDs]}}; + {ok, #rwalk{qids = [binary_to_qid(QID) || <> <= QIDs]}}; -do_parse(?Tread, <>) -> - {ok, tread, #{fid => FID, offset => Offset, count => Count}}; +do_parse(?Tread, <>) -> + {ok, #tread{fid = FID, offset = Offset, len = Len}}; do_parse(?Rread, <>) -> - {ok, rread, #{data => Data}}; + {ok, #rread{data = Data}}; do_parse(Type, Data) -> {error, {invalid_message, Type, Data}}. @@ -193,80 +194,80 @@ parse_stat(<<_Size:2/?int, }}; parse_stat(_) -> {error, invalid_stat_data}. --spec encode(Tag :: tag() | notag, Type :: message_type(), Data :: map()) -> iodata(). -encode(Tag, Type, Data) -> - {MT, Encoded} = do_encode(Type, Data), +-spec encode(Tag :: tag() | notag, Data :: message()) -> iodata(). +encode(Tag, Data) -> + {MT, Encoded} = do_encode(Data), Tag0 = case Tag of notag -> ?notag; V -> V end, [<> | Encoded]. -do_encode(tversion, #{max_packet_size := MSize, version := Version}) -> +do_encode(#tversion{max_packet_size = MSize, version = Version}) -> {?Tversion, [<> | encode_str(Version)]}; -do_encode(rversion, #{max_packet_size := MSize, version := Version}) -> +do_encode(#rversion{max_packet_size = MSize, version = Version}) -> {?Rversion, [<> | encode_str(Version)]}; -do_encode(tauth, #{afid := AFID, uname := Uname, aname := Aname}) -> +do_encode(#tauth{afid = AFID, uname = Uname, aname = Aname}) -> {?Tauth, [<>, encode_str(Uname), encode_str(Aname)]}; -do_encode(rauth, #{aqid := AQID}) -> +do_encode(#rauth{aqid = AQID}) -> {?Rauth, qid_to_binary(AQID)}; -do_encode(tattach, #{fid := FID, afid := AFID, uname := Uname, aname := Aname}) -> +do_encode(#tattach{fid = FID, afid = AFID, uname = Uname, aname = Aname}) -> {?Tattach, [<>, encode_str(Uname), encode_str(Aname)]}; -do_encode(rattach, #{qid := QID}) -> +do_encode(#rattach{qid = QID}) -> {?Rattach, qid_to_binary(QID)}; -do_encode(tclunk, #{fid := FID}) -> +do_encode(#tclunk{fid = FID}) -> {?Tclunk, <>}; -do_encode(rclunk, _) -> +do_encode(#rclunk{}) -> {?Rclunk, []}; -do_encode(rerror, #{error := Error}) -> +do_encode(#rerror{msg = Error}) -> {?Rerror, encode_str(Error)}; -do_encode(tflush, #{tag := Tag}) -> +do_encode(#tflush{tag = Tag}) -> {?Tflush, <>}; -do_encode(rflush, _) -> +do_encode(#rflush{}) -> {?Rflush, []}; -do_encode(topen, #{fid := FID, mode := Mode}) -> +do_encode(#topen{fid = FID, mode = Mode}) -> {?Topen, <>}; -do_encode(ropen, #{qid := QID, io_unit := IOUnit}) -> +do_encode(#ropen{qid = QID, io_unit = IOUnit}) -> {?Ropen, [qid_to_binary(QID), <>]}; -do_encode(tcreate, #{fid := FID, name := Name, perm := Perm, mode := Mode}) -> +do_encode(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}) -> {?Tcreate, [<>, encode_str(Name), <>]}; -do_encode(rcreate, #{qid := QID, io_unit := IOUnit}) -> +do_encode(#rcreate{qid = QID, io_unit = IOUnit}) -> {?Rcreate, [qid_to_binary(QID), <>]}; -do_encode(tremove, #{fid := FID}) -> +do_encode(#tremove{fid = FID}) -> {?Tremove, <>}; -do_encode(rremove, _) -> +do_encode(#rremove{}) -> {?Rremove, []}; -do_encode(tstat, #{fid := FID}) -> +do_encode(#tstat{fid = FID}) -> {?Tstat, <>}; -do_encode(rstat, #{stat := Stat}) -> +do_encode(#rstat{stat = Stat}) -> {?Rstat, encode_stat(Stat)}; -do_encode(twstat, #{fid := FID, stat := Stat}) -> +do_encode(#twstat{fid = FID, stat = Stat}) -> {?Twstat, [<>, encode_stat(Stat)]}; -do_encode(rwstat, _) -> +do_encode(#rwstat{}) -> {?Rwstat, []}; -do_encode(twalk, #{fid := FID, new_fid := NewFID, names := Names}) -> +do_encode(#twalk{fid = FID, new_fid = NewFID, names = Names}) -> ENames = [encode_str(Name) || Name <- Names], Len = length(ENames), {?Twalk, [<> | ENames]}; -do_encode(rwalk, #{qids := QIDs}) -> +do_encode(#rwalk{qids = QIDs}) -> EQIDs = [qid_to_binary(QID) || QID <- QIDs], Len = length(EQIDs), {?Rwalk, [<> | EQIDs]}; -do_encode(tread, #{fid := FID, offset := Offset, count := Count}) -> - {?Tread, <>}; -do_encode(rread, #{data := Data}) -> +do_encode(#tread{fid = FID, offset = Offset, len = Len}) -> + {?Tread, <>}; +do_encode(#rread{data = Data}) -> {?Rread, encode_str(Data)}. encode_stat(#{ diff --git a/src/e9p_server.erl b/src/e9p_server.erl index 997c0a5..c2ef189 100644 --- a/src/e9p_server.erl +++ b/src/e9p_server.erl @@ -4,6 +4,8 @@ -module(e9p_server). +-include("e9p_internal.hrl"). + -include_lib("kernel/include/logger.hrl"). -export([start_link/2, @@ -26,6 +28,7 @@ setup_acceptor(Parent, Port, Handler0) -> accept_loop(LSock, Handler) -> case gen_tcp:accept(LSock, 5000) of {ok, Sock} -> + % TODO: Handle connected clients in separate process ok = ?MODULE:loop(Sock, #{}, Handler), ?MODULE:accept_loop(LSock, Handler); {error, timeout} -> @@ -36,52 +39,107 @@ accept_loop(LSock, Handler) -> loop(Sock, FIDs, Handler) -> case e9p_transport:read(Sock) of - {ok, Tag, Type, Data} -> - case handle_message(Type, Data, FIDs, Handler) of - {ok, {RType, RData}, RFIDs, RHandler} -> - e9p_transport:send(Sock, Tag, RType, RData), - ?MODULE:loop(Sock, RFIDs, RHandler) + {ok, Tag, Data} -> + case handle_message(Data, FIDs, Handler) of + {ok, Reply, RFIDs, RHandler} -> + e9p_transport:send(Sock, Tag, Reply), + ?MODULE:loop(Sock, RFIDs, RHandler); + {error, Err, RHandler} -> + e9p_transport:send(Sock, Tag, error_msg(Err)), + ?MODULE:loop(Sock, FIDs, RHandler) end; {error, closed} -> ?LOG_WARNING("Connection closed"), ok end. -handle_message(tversion, #{version := ~"9P2000"} = Data, FIDs, Handler) -> - {ok, {rversion, Data}, FIDs, Handler}; -handle_message(tattach, Data, FIDs, Handler0) -> - #{fid := FID, uname := _UName, aname := AName} = Data, - {ok, QID, Handler} = e9p_fs:root(Handler0, AName), - NFIDs = FIDs#{FID => QID}, - {ok, {rattach, #{qid => QID}}, NFIDs, Handler}; -handle_message(tclunk, #{fid := FID}, FIDs, Handler) -> - NFIDs = maps:remove(FID, FIDs), - {ok, {rflush, #{}}, NFIDs, Handler}; -handle_message(twalk, Data, FIDs, Handler0) -> - #{ - fid := FID, - new_fid := NewFID, - names := Paths - } = Data, - #{FID := QID} = FIDs, - {ok, NewQID, QIDs, Handler} = e9p_fs:walk(Handler0, QID, Paths), - {ok, {rwalk, #{qids => QIDs}}, FIDs#{NewFID => NewQID}, Handler}; -handle_message(topen, #{fid := FID}, FIDs, Handler0) -> - #{FID := QID} = FIDs, - {ok, IOUnit, Handler} = e9p_fs:open(Handler0, QID), - {ok, {ropen, #{qid => QID, iounit => IOUnit}}, FIDs, Handler}; -handle_message(tcreate, #{fid := FID}, FIDs, Handler0) -> - #{FID := QID, name := Name, perm := Perm, mode := Mode} = FIDs, - {ok, {NewQID, IOUnit}, Handler} = e9p_fs:create(Handler0, QID, Name, Perm, Mode), - {ok, {rcreate, #{qid => NewQID, iounit => IOUnit}}, FIDs, Handler}; -handle_message(tread, Data, FIDs, Handler0) -> - #{ - fid := FID, - offset := Offset, - count := Count - } = Data, - #{FID := QID} = FIDs, - {ok, Data, Handler} = e9p_fs:read(Handler0, QID, Offset, Count), - {ok, {rread, #{data => Data}}, FIDs, Handler}; -handle_message(_Type, _Data, FIDs, Handler) -> - {ok, {rerror, #{error => ~"Unknown request type"}}, FIDs, Handler}. +handle_message(#tversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler) -> + % Currently only "basic" 9p2000 version is supported, without any extensions + % like `.u` or `.L` + {ok, #rversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler}; + +handle_message(#tflush{}, FIDs, Handler) -> + % Currently there is no support for parallel messages, so this does simply + % nothing + {ok, #rflush{}, FIDs, Handler}; + +handle_message(#tattach{fid = FID, uname = _UName, aname = AName}, FIDs, Handler0) -> + maybe + {ok, QID, Handler} ?= e9p_fs:root(Handler0, AName), + NFIDs = FIDs#{FID => QID}, + {ok, #rattach{qid = QID}, NFIDs, Handler} + end; + +handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, NewQID, QIDs, Handler} ?= e9p_fs:walk(Handler0, QID, Paths), + {ok, #rwalk{qids = QIDs}, FIDs#{NewFID => NewQID}, Handler} + end; + +handle_message(#topen{fid = FID}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID), + {ok, #ropen{qid = QID, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} + end; +handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:create(Handler0, QID, Name, Perm, Mode), + {ok, #rcreate{qid = NewQID, io_unit = IOUnit}, FIDs, Handler} + end; + +handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, Data, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len), + {ok, #rread{data = Data}, FIDs, Handler} + end; +handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, Data, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), + {ok, #rread{data = Data}, FIDs, Handler} + end; + +handle_message(#tclunk{fid = FID}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, Handler} ?= e9p_fs:clunk(Handler0, QID), + NFIDs = maps:remove(FID, FIDs), + {ok, #rclunk{}, NFIDs, Handler} + end; + +handle_message(#tremove{fid = FID}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, Handler} ?= e9p_fs:remove(Handler0, QID), + {ok, #rremove{}, FIDs, Handler} + end; + +handle_message(#tstat{fid = FID}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, Stat, Handler} ?= e9p_fs:stat(Handler0, QID), + {ok, #rstat{stat = Stat}, FIDs, Handler} + end; + +handle_message(#twstat{fid = FID, stat = Stat}, FIDs, Handler0) -> + maybe + {ok, QID} ?= get_qid(FIDs, FID), + {ok, Handler} ?= e9p_fs:wstat(Handler0, QID, Stat), + {ok, #rwstat{}, FIDs, Handler} + end; + +handle_message(_Msg, _FIDs, Handler) -> + {error, ~"Unknown request type", Handler}. + +get_qid(FIDs, FID) -> + case FIDs of + #{FID := QID} -> {ok, QID}; + _ -> {error, io_lib:fwrite(~"Unknown FID: ~B", [FID])} + end. + +error_msg(Data) -> + #rerror{msg = io_lib:fwrite(~"~p", [Data])}. diff --git a/src/e9p_transport.erl b/src/e9p_transport.erl index b4056b8..c19dea0 100644 --- a/src/e9p_transport.erl +++ b/src/e9p_transport.erl @@ -6,10 +6,10 @@ -include("e9p_internal.hrl"). --export([send/4, read/1, read_stream/1]). +-export([send/3, read/1, read_stream/1]). -send(Socket, Tag, Type, Message) -> - Encoded = e9p_msg:encode(Tag, Type, Message), +send(Socket, Tag, Message) -> + Encoded = e9p_msg:encode(Tag, Message), Size = iolist_size(Encoded) + 4, gen_tcp:send(Socket, [<>, Encoded]). @@ -19,8 +19,8 @@ read(Socket) -> case gen_tcp:recv(Socket, Size - 4) of {ok, Data} when is_binary(Data) -> case e9p_msg:parse(Data) of - {ok, Tag, Type, Msg} -> - {ok, Tag, Type, Msg}; + {ok, Tag, Msg} -> + {ok, Tag, Msg}; {error, _} = Error -> Error end; @@ -33,8 +33,8 @@ read(Socket) -> read_stream(<> = Input) -> case e9p_msg:parse(Data) of - {ok, Tag, Type, Msg} -> - {ok, Tag, Type, Msg, Rest}; + {ok, Tag, Msg} -> + {ok, Tag, Msg, Rest}; {error, Error} -> {error, Error, Input} end; diff --git a/src/e9p_unfs.erl b/src/e9p_unfs.erl index c2dc9e2..ecb3c18 100644 --- a/src/e9p_unfs.erl +++ b/src/e9p_unfs.erl @@ -8,7 +8,7 @@ -include_lib("kernel/include/file.hrl"). --export([init/1, root/2, walk/3, stat/2, read/4, write/4]). +-export([init/1, root/2, walk/3, stat/2, open/2, read/4]). init(#{path := Path}) -> {ok, #{root => Path}}. @@ -21,18 +21,46 @@ walk(#{state := Path}, File, State) -> Next = filename:join(Path, File), case file:read_file_info(Next, [{time, posix}]) of {ok, #file_info{type = Type, inode = Inode}} -> - NQid = e9p:make_qid(Type, 0, Inode, Next), + NQid = e9p:make_qid(Type, 0, Inode, {Next, []}), {NQid, State}; {error, _} -> {false, State} end. -stat(_Qid, State) -> +stat(#{state := {Path, []}} = QID, State) -> + case file:read_file_info(Path, [{time, posix}]) of + {ok, FileInfo} -> + Stat = file_info_to_stat(QID, FileInfo), + {ok, Stat, State}; + {error, Error} -> + {error, Error, State} + end. + +open(_Qid, State) -> {error, unimplemented, State}. read(_Qid, _Offset, _Len, State) -> {error, unimplemented, State}. -write(_Qid, _Offset, _Data, State) -> - {error, unimplemented, State}. +file_info_to_stat( + #{state := Path} = QID, + #file_info{ + size = Len, + atime = Atime, + mtime = Mtime, + mode = Mode + }) -> + #{ + type => 0, + dev => 0, + qid => QID, + mode => Mode, + atime => Atime, + mtime => Mtime, + length => Len, + name => filename:basename(Path), + uid => ~"", + gid => ~"", + muid => ~"" + }. diff --git a/src/e9p_utils.erl b/src/e9p_utils.erl index 7c06017..d05c9ed 100644 --- a/src/e9p_utils.erl +++ b/src/e9p_utils.erl @@ -4,7 +4,7 @@ -module(e9p_utils). --export([normalize_path/1, qtype_from_atom/1]). +-export([normalize_path/1, to_qtype/1]). normalize_path(List) -> normalize_path(List, []). @@ -16,11 +16,18 @@ normalize_path([Dot | Rest], Acc) normalize_path([P | Rest], Acc) -> normalize_path(Rest, [P | Acc]). -qtype_from_atom(dir) -> 16#80; -qtype_from_atom(append) -> 16#40; -qtype_from_atom(excl) -> 16#20; -qtype_from_atom(device) -> 16#10; -qtype_from_atom(auth) -> 16#08; -qtype_from_atom(tmp) -> 16#04; -qtype_from_atom(symlink) -> 16#02; -qtype_from_atom(regular) -> 16#00. +to_qtype(List) when is_list(List) -> + lists:foldl( + fun(El, Acc) when is_integer(Acc) -> to_qtype(El) bor Acc end, + 0, + List + ); + +to_qtype(dir) -> 16#80; +to_qtype(append) -> 16#40; +to_qtype(excl) -> 16#20; +to_qtype(device) -> 16#10; +to_qtype(auth) -> 16#08; +to_qtype(tmp) -> 16#04; +to_qtype(symlink) -> 16#02; +to_qtype(regular) -> 16#00. diff --git a/test/prop_e9p_msg.erl b/test/prop_e9p_msg.erl index 97e1b0c..f88c109 100644 --- a/test/prop_e9p_msg.erl +++ b/test/prop_e9p_msg.erl @@ -4,17 +4,39 @@ -module(prop_e9p_msg). +-include("e9p_internal.hrl"). -include_lib("proper/include/proper.hrl"). % -include_lib("stdlib/include/assert.hrl"). +afid() -> integer(16#0000, 16#FFFF). + +prop_can_decode_encoded_tversion() -> + ?FORALL({Version, MPS}, {binary(), afid()}, + begin + enc_dec(#tversion{version = Version, max_packet_size = MPS}) + end). + +prop_can_decode_encoded_rversion() -> + ?FORALL({Version, MPS}, {binary(), afid()}, + begin + enc_dec(#rversion{version = Version, max_packet_size = MPS}) + end). + prop_can_decode_encoded_tauth() -> - ?FORALL({Uname, Aname}, {binary(), binary()}, + ?FORALL({Afid, Uname, Aname}, {integer(0, 16#FFFF), binary(), binary()}, begin - enc_dec(tauth, #{afid => 1, uname => Uname, aname => Aname}) + enc_dec(#tauth{afid = Afid, uname = Uname, aname = Aname}) end). -enc_dec(Kind, Data) -> +prop_can_decode_encoded_rerror() -> + ?FORALL({Msg}, {binary()}, + begin + enc_dec(#rerror{msg = Msg}) + end). + + +enc_dec(Data) -> Tag = 1, - Out = e9p_msg:encode(Tag, Kind, Data), + Out = e9p_msg:encode(Tag, Data), Encoded = iolist_to_binary(Out), - {ok, Tag, Kind, Data} =:= e9p_msg:parse(Encoded). + {ok, Tag, Data} =:= e9p_msg:parse(Encoded).