Pure Erlang implementation of 9p2000 protocol
filesystem fs 9p2000 erlang 9p
at master 175 lines 6.0 kB view raw
1% SPDX-FileCopyrightText: 2026 Łukasz Niemier <~@hauleth.dev> 2% 3% SPDX-License-Identifier: Apache-2.0 4 5-module(e9p_server). 6 7-include("e9p_internal.hrl"). 8 9-include_lib("kernel/include/logger.hrl"). 10 11-export([start/2, 12 start_link/2]). 13 14-export([setup_acceptor/3, 15 accept_loop/2, 16 loop/1 17 ]). 18 19-record(state, { 20 socket, 21 % trans_mod = gen_tcp, 22 % ver, 23 fids = #{}, 24 handler 25 }). 26 27start(Port, Handler) -> 28 proc_lib:start(?MODULE, setup_acceptor, [self(), Port, Handler]). 29 30start_link(Port, Handler) -> 31 proc_lib:start_link(?MODULE, setup_acceptor, [self(), Port, Handler]). 32 33setup_acceptor(Parent, Port, Handler0) -> 34 maybe 35 {ok, LSock} ?= gen_tcp:listen(Port, [binary, {active, false}]), 36 {ok, Handler} ?= e9p_fs:init(Handler0), 37 38 proc_lib:init_ack(Parent, {ok, self()}), 39 40 ?MODULE:accept_loop(LSock, Handler) 41 else 42 {error, _} = Error -> 43 proc_lib:init_fail(Parent, Error) 44 end. 45 46accept_loop(LSock, Handler) -> 47 case gen_tcp:accept(LSock, 5000) of 48 {ok, Sock} -> 49 % TODO: Handle connected clients in separate process 50 51 State = #state{ 52 socket = Sock, 53 handler = Handler 54 }, 55 ok = ?MODULE:loop(State), 56 ?MODULE:accept_loop(LSock, Handler); 57 {error, timeout} -> 58 ?MODULE:accept_loop(LSock, Handler); 59 {error, closed} -> 60 ok 61 end. 62 63loop(#state{socket = Sock} = State) -> 64 case e9p_transport:read(Sock) of 65 {ok, Tag, Data} -> 66 ?LOG_DEBUG(#{message => Data, tag => Tag}), 67 try handle_message(Data, State#state.fids, State#state.handler) of 68 {ok, Reply, FIDs, Handler} -> 69 e9p_transport:send(Sock, Tag, Reply), 70 ?MODULE:loop(State#state{fids = FIDs, handler = Handler}); 71 {error, Err, RHandler} -> 72 e9p_transport:send(Sock, Tag, #rerror{msg = Err}), 73 ?MODULE:loop(State#state{handler = RHandler}) 74 catch 75 C:E:S -> 76 ?LOG_ERROR(#{ 77 kind => C, 78 msg => E, 79 stacktrace => S 80 }), 81 e9p_transport:send(Sock, Tag, #rerror{msg = io_lib:format("Caught ~p: ~p", [C, E])}), 82 ?MODULE:loop(State) 83 end; 84 {error, closed} -> 85 ?LOG_INFO("Connection closed"), 86 ok 87 end. 88 89handle_message(#tversion{version = <<"9P2000", _/binary>>, max_packet_size = MPS}, FIDs, Handler) -> 90 % Currently only "basic" 9p2000 version is supported, without any extensions 91 % like `.u` or `.L` 92 {ok, #rversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler}; 93 94handle_message(#tflush{}, FIDs, Handler) -> 95 % Currently there is no support for parallel messages, so this does simply 96 % nothing 97 {ok, #rflush{}, FIDs, Handler}; 98 99handle_message(#tattach{fid = FID, uname = UName, aname = AName}, FIDs, Handler0) -> 100 maybe 101 {ok, QID, Handler} ?= e9p_fs:root(Handler0, UName, AName), 102 NFIDs = FIDs#{FID => QID}, 103 {ok, #rattach{qid = QID#fid.qid}, NFIDs, Handler} 104 end; 105 106handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) -> 107 maybe 108 {ok, QID} ?= get_qid(FIDs, FID), 109 {ok, {NewQID, QIDs}, Handler} ?= e9p_fs:walk(Handler0, QID, Paths), 110 {ok, #rwalk{qids = QIDs}, FIDs#{NewFID => NewQID}, Handler} 111 end; 112 113handle_message(#topen{fid = FID, mode = Mode}, FIDs, Handler0) -> 114 maybe 115 {ok, QID} ?= get_qid(FIDs, FID), 116 {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID, Mode), 117 {ok, #ropen{qid = QID#fid.qid, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} 118 end; 119handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) -> 120 maybe 121 {ok, QID} ?= get_qid(FIDs, FID), 122 {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:create(Handler0, QID, Name, Perm, Mode), 123 {ok, #rcreate{qid = NewQID#fid.qid, io_unit = IOUnit}, FIDs, Handler} 124 end; 125 126handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> 127 maybe 128 {ok, QID} ?= get_qid(FIDs, FID), 129 {ok, {NQID, Data}, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len), 130 {ok, #rread{data = Data}, FIDs#{FID => NQID}, Handler} 131 end; 132handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) -> 133 maybe 134 {ok, QID} ?= get_qid(FIDs, FID), 135 {ok, {NQID, Len}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), 136 {ok, #rwrite{len = Len}, FIDs#{FID => NQID}, Handler} 137 end; 138 139handle_message(#tclunk{fid = FID}, FIDs, Handler0) -> 140 maybe 141 {ok, QID} ?= get_qid(FIDs, FID), 142 {ok, Handler} ?= e9p_fs:clunk(Handler0, QID), 143 NFIDs = maps:remove(FID, FIDs), 144 {ok, #rclunk{}, NFIDs, Handler} 145 end; 146 147handle_message(#tremove{fid = FID}, FIDs, Handler0) -> 148 maybe 149 {ok, QID} ?= get_qid(FIDs, FID), 150 {ok, Handler} ?= e9p_fs:remove(Handler0, QID), 151 {ok, #rremove{}, FIDs, Handler} 152 end; 153 154handle_message(#tstat{fid = FID}, FIDs, Handler0) -> 155 maybe 156 {ok, QID} ?= get_qid(FIDs, FID), 157 {ok, Stat, Handler} ?= e9p_fs:stat(Handler0, QID), 158 {ok, #rstat{stat = Stat}, FIDs, Handler} 159 end; 160 161handle_message(#twstat{fid = FID, stat = Stat}, FIDs, Handler0) -> 162 maybe 163 {ok, QID} ?= get_qid(FIDs, FID), 164 {ok, Handler} ?= e9p_fs:wstat(Handler0, QID, Stat), 165 {ok, #rwstat{}, FIDs, Handler} 166 end; 167 168handle_message(_Msg, _FIDs, Handler) -> 169 {error, ~"Unknown request type", Handler}. 170 171get_qid(FIDs, FID) -> 172 case FIDs of 173 #{FID := QID} -> {ok, QID}; 174 _ -> {error, io_lib:fwrite(~"Unknown FID: ~B", [FID])} 175 end.