Pure Erlang implementation of 9p2000 protocol
filesystem fs 9p2000 erlang 9p
at master 96 lines 3.0 kB view raw
1% SPDX-FileCopyrightText: 2025 Łukasz Niemier <~@hauleth.dev> 2% 3% SPDX-License-Identifier: Apache-2.0 4 5-module(e9p_client). 6 7-include("e9p_internal.hrl"). 8-include_lib("kernel/include/logger.hrl"). 9 10-behaviour(gen_server). 11 12-export([attach/3]). 13-export([start_link/3]). 14-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). 15 16attach(Client, Uname, Aname) -> 17 gen_server:call(Client, {attach, noauth, Uname, Aname}). 18 19start_link(Host, Port, Opts) -> 20 gen_server:start_link(?MODULE, {Host, Port, Opts}, []). 21 22init({Host, Port, _Opts}) -> 23 {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, binary]), 24 case version_negotiation(Socket) of 25 {ok, #rversion{max_packet_size = MaxPacketSize, version = ?version}} -> 26 inet:setopts(Socket, [{active, once}]), 27 {ok, 28 #{socket => Socket, 29 buffer => <<>>, 30 tag => 0, 31 fid => 0, 32 msgs => #{}, 33 max_packet_size => MaxPacketSize, 34 version => ?version}}; 35 {ok, #rversion{version = OtherVersion}} -> 36 {error, {unsupported_version, OtherVersion}}; 37 {error, _} = Error -> 38 Error 39 end. 40 41handle_call({attach, Auth, Uname, Aname}, From, State) -> 42 #{tag := Tag, 43 fid := Fid, 44 socket := Socket, 45 msgs := Msgs} = 46 State, 47 Afid = 48 case Auth of 49 noauth -> 50 ?nofid; 51 Id -> 52 Id 53 end, 54 Msg = #tattach{fid = Fid, 55 afid = Afid, 56 uname = Uname, 57 aname = Aname}, 58 e9p_transport:send(Socket, Tag, Msg), 59 {noreply, 60 State#{tag := Tag + 1, 61 fid := Fid + 1, 62 msgs := Msgs#{Tag => {From, #{fid => Fid}}}}}; 63handle_call(_Msg, _From, State) -> 64 {reply, {error, not_implemented}, State}. 65 66handle_cast(_Msg, State) -> 67 {noreply, State}. 68 69handle_info({tcp, Socket, Data}, #{socket := Socket} = State) -> 70 #{buffer := Buffer, msgs := Msgs0} = State, 71 inet:setopts(Socket, [{active, once}]), 72 case e9p_transport:read_stream(<<Buffer/binary, Data/binary>>) of 73 {ok, Tag, Msg, Rest} -> 74 Msgs = 75 case maps:take(Tag, Msgs0) of 76 {{From, _}, M} -> 77 gen_server:reply(From, {ok, Msg}), 78 M; 79 error -> 80 ?LOG_WARNING("Unknown tag ~p", [Tag]), 81 Msgs0 82 end, 83 {noreply, State#{buffer := Rest, msgs := Msgs}}; 84 {more, Data} -> 85 {noreply, State#{buffer := Data}} 86 end. 87 88version_negotiation(Socket) -> 89 Msg = #tversion{max_packet_size = ?max_packet_size, version = ?version}, 90 e9p_transport:send(Socket, notag, Msg), 91 case e9p_transport:read(Socket) of 92 {ok, _, Resp} -> 93 {ok, Resp}; 94 {error, _} = Error -> 95 Error 96 end.