Current section
Files
Jump to
Current section
Files
src/e9p_server.erl
% SPDX-FileCopyrightText: 2026 Łukasz Niemier <~@hauleth.dev>
%
% SPDX-License-Identifier: Apache-2.0
-module(e9p_server).
-include("e9p_internal.hrl").
-include_lib("kernel/include/logger.hrl").
-export([start_link/2,
setup_acceptor/3,
accept_loop/2,
loop/1
]).
-record(state, {
socket,
% trans_mod = gen_tcp,
% ver,
fids = #{},
handler
}).
start_link(Port, Handler) ->
proc_lib:start_link(?MODULE, setup_acceptor, [self(), Port, Handler]).
setup_acceptor(Parent, Port, Handler0) ->
maybe
{ok, LSock} ?= gen_tcp:listen(Port, [binary, {active, false}]),
{ok, Handler} ?= e9p_fs:init(Handler0),
proc_lib:init_ack(Parent, {ok, self()}),
?MODULE:accept_loop(LSock, Handler)
else
{error, _} = Error ->
proc_lib:init_fail(Parent, Error)
end.
accept_loop(LSock, Handler) ->
case gen_tcp:accept(LSock, 5000) of
{ok, Sock} ->
% TODO: Handle connected clients in separate process
State = #state{
socket = Sock,
handler = Handler
},
ok = ?MODULE:loop(State),
?MODULE:accept_loop(LSock, Handler);
{error, timeout} ->
?MODULE:accept_loop(LSock, Handler);
{error, closed} ->
ok
end.
loop(#state{socket = Sock} = State) ->
case e9p_transport:read(Sock) of
{ok, Tag, Data} ->
?LOG_DEBUG(#{message => Data, tag => Tag}),
try handle_message(Data, State#state.fids, State#state.handler) of
{ok, Reply, FIDs, Handler} ->
e9p_transport:send(Sock, Tag, Reply),
?MODULE:loop(State#state{fids = FIDs, handler = Handler});
{error, Err, RHandler} ->
e9p_transport:send(Sock, Tag, #rerror{msg = Err}),
?MODULE:loop(State#state{handler = RHandler})
catch
C:E:S ->
?LOG_ERROR(#{
kind => C,
msg => E,
stacktrace => S
}),
e9p_transport:send(Sock, Tag, #rerror{msg = io_lib:format("Caught ~p: ~p", [C, E])}),
?MODULE:loop(State)
end;
{error, closed} ->
?LOG_INFO("Connection closed"),
ok
end.
handle_message(#tversion{version = <<"9P2000", _/binary>>, 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, UName, AName),
NFIDs = FIDs#{FID => QID},
{ok, #rattach{qid = QID#fid.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, mode = Mode}, FIDs, Handler0) ->
maybe
{ok, QID} ?= get_qid(FIDs, FID),
{ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID, Mode),
{ok, #ropen{qid = QID#fid.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#fid.qid, 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, {NQID, Data}, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len),
{ok, #rread{data = Data}, FIDs#{FID => NQID}, Handler}
end;
handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) ->
maybe
{ok, QID} ?= get_qid(FIDs, FID),
{ok, {NQID, Len}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data),
{ok, #rwrite{len = Len}, FIDs#{FID => NQID}, 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.