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

Rewrite e9p_fs implementation to do not allow QID modification

This makes implementing new filesystems easier as author do not need to
be concerned about FIDs, QIDs, and stuff, instead it will be handled for
them in most cases.

hauleth.dev 4d2f54c4 d81e5d5c

verified
+216 -143
+7 -22
src/e9p.erl
··· 4 4 5 5 -module(e9p). 6 6 7 - -export([make_qid/4, is_type/2]). 7 + -export([make_qid/3, is_type/2]). 8 8 9 9 -export_type([qid/0, fid/0]). 10 10 11 11 -export_type([u8/0, u16/0, u32/0, u64/0]). 12 + 13 + -include("e9p_internal.hrl"). 12 14 13 15 -type u8() :: 16#00..16#FF. 14 16 -type u16() :: 16#0000..16#FFFF. 15 17 -type u32() :: 16#00000000..16#FFFFFFFF. 16 18 -type u64() :: 16#0000000000000000..16#FFFFFFFFFFFFFFFF. 17 19 18 - -type qid() :: #{ 19 - type => u8(), 20 - version => u16(), 21 - path => u64(), 22 - state => term() 23 - }. 20 + -opaque qid() :: #qid{type :: u8(), version :: u32(), path :: u64()}. 24 21 25 22 -type fid() :: 16#00000000..16#FFFFFFFF. 26 23 27 24 %-spec make_qid() 28 - make_qid(Type, Version, Path, State) -> 29 - #{ 30 - type => to_qtype(Type), 31 - version => Version, 32 - path => Path, 33 - state => State 34 - }. 25 + make_qid(Type, Version, Path) -> 26 + #qid{type = to_qtype(Type), version = Version, path = Path}. 35 27 36 - is_type(#{type := QType}, Type) -> 28 + is_type(#qid{type = QType}, Type) -> 37 29 (to_qtype(Type) band QType) =/= 0. 38 - 39 - to_qtype(List) when is_list(List) -> 40 - lists:foldl( 41 - fun(El, Acc) when is_integer(Acc) -> to_qtype(El) bor Acc end, 42 - 0, 43 - List 44 - ); 45 30 46 31 to_qtype(directory) -> 16#80; 47 32 to_qtype(append) -> 16#40;
+98 -63
src/e9p_fs.erl
··· 20 20 wstat/3 21 21 ]). 22 22 23 - -export_type([state/0]). 23 + -include("e9p_internal.hrl"). 24 + -include_lib("kernel/include/logger.hrl"). 25 + 26 + -export_type([state/0, fid/0, path/0, result/0, result/1]). 24 27 25 28 -type state() :: term(). 29 + -type fid() :: {QID :: e9p:qid(), State :: fid_state()}. 30 + -type path() :: [unicode:chardata()]. 31 + -type fid_state() :: term(). 26 32 -type result() :: {ok, state()} | {error, term(), state()}. 27 33 -type result(T) :: {ok, T, state()} | {error, term(), state()}. 28 34 ··· 48 54 %% If implementation provides multiple trees then the `AName' will be set to the 49 55 %% tree defined by the client. It is left to the implementation to ensure the 50 56 %% constraints of the file root (aka `walk(Root, "..", State0) =:= {Root, State1}'. 51 - -callback root(UName :: unicode:chardata(), AName :: unicode:chardata(), state()) -> {ok, e9p:qid(), state()}. 57 + -callback root(UName :: unicode:chardata(), AName :: unicode:chardata(), state()) -> 58 + {ok, fid_state(), state()}. 52 59 53 - -callback flush(state()) -> {ok, state()} | {error, term(), state()}. 60 + -callback flush(state()) -> result(). 54 61 55 62 %% Walk through the given path starting at the `QID' 56 - -callback walk(QID :: e9p:qid(), unicode:chardata(), state()) -> 57 - {e9p:qid() | false, state()}. 63 + -callback walk(fid(), File :: unicode:chardata(), unicode:chardata(), state()) -> 64 + {fid() | false, state()}. 58 65 59 - -callback open(QID :: e9p:qid(), Mode :: integer(), state()) -> result({e9p:qid(), e9p:u32()}). 66 + -callback open(fid(), path(), Mode :: integer(), state()) -> result({fid_state(), e9p:u32()}). 60 67 61 - -callback create(QID :: e9p:qid(), 68 + -callback create(fid(), 69 + path(), 62 70 Name :: unicode:chardata(), 63 71 Perm :: e9p:u32(), 64 72 Mode :: e9p:u8(), 65 - state()) -> result({e9p:qid(), e9p:u32()}). 73 + state()) -> result({fid(), IOUnit :: e9p:u32()}). 66 74 67 75 %% Read data from file indicated by `QID' 68 - -callback read(QID :: e9p:qid(), 76 + -callback read(fid(), 77 + path(), 69 78 Offset :: non_neg_integer(), 70 79 Length :: non_neg_integer(), 71 - state()) -> result({e9p:qid(), iodata()}). 80 + state()) -> result({fid_state(), iodata()}). 72 81 73 82 %% Write data to file indicated by `QID' 74 - -callback write(QID :: e9p:qid(), 83 + -callback write(fid(), 84 + path(), 75 85 Offset :: non_neg_integer(), 76 86 Data :: iodata(), 77 - state()) -> result({e9p:qid(), non_neg_integer()}). 87 + state()) -> result({fid_state(), non_neg_integer()}). 78 88 79 - -callback clunk(QID :: e9p:qid(), state()) -> result(). 89 + -callback clunk(fid(), path(), state()) -> result(). 80 90 81 - -callback remove(QID :: e9p:qid(), state()) -> result(). 91 + -callback remove(fid(), path(), state()) -> result(). 82 92 83 93 %% Return stat data for file indicated by `QID' 84 - -callback stat(QID :: e9p:qid(), state()) -> result(map()). 94 + -callback stat(fid(), path(), state()) -> result(map()). 85 95 86 96 %% Write stat data for file indicated by `QID' 87 - -callback wstat(QID :: e9p:qid(), map(), state()) -> result(). 97 + -callback wstat(fid(), path(), map(), state()) -> result(). 88 98 89 99 -optional_callbacks([ 90 100 flush/1, 91 - walk/3, 92 - open/3, 93 - create/5, 94 - read/4, 95 - write/4, 96 - clunk/2, 97 - remove/2, 98 - stat/2, 99 - wstat/3 101 + clunk/3 100 102 ]). 101 103 102 104 init({Mod, State}) -> ··· 107 109 108 110 root({Mod, State}, UName, AName) -> 109 111 case Mod:root(UName, AName, State) of 110 - {ok, QID, NewState} -> 111 - {ok, QID, {Mod, NewState}} 112 + {ok, {QID, FState}, NewState} -> 113 + {ok, #fid{qid = QID, path = [], state = FState}, {Mod, NewState}} 112 114 end. 113 115 114 116 -doc """ 115 117 Walk through paths starting at QID. 116 118 """. 117 - walk({Mod, State}, QID, Paths) when is_atom(Mod) -> 118 - ?if_supported(do_walk(Mod, QID, Paths, State, [])). 119 + walk({Mod, State0}, FID0, Paths) when is_atom(Mod) -> 120 + case do_walk(Mod, FID0, Paths, State0, []) of 121 + {ok, {FID, QIDs}, State} -> {ok, {FID, QIDs}, {Mod, State}}; 122 + {error, Reason, State} -> {error, Reason, {Mod, State}} 123 + end. 124 + 125 + do_walk(_Mod, FID, [], State, Acc) -> 126 + {ok, {FID, lists:reverse(Acc)}, State}; 127 + do_walk(Mod, #fid{qid = QID0, path = Path, state = FState0} = FID0, [P | Rest], State0, Acc) -> 128 + case e9p:is_type(QID0, directory) of 129 + true -> 130 + case Mod:walk({QID0, FState0}, Path, P, State0) of 131 + {false, State} when Acc =:= [] -> 132 + % Per specification walk to first entry in name list must succeed 133 + % (if any) otherwise return error. In subsequent steps we return 134 + % successful list and last succeeded QID 135 + {error, io_lib:format("Failed walk to ~p", [P]), State}; 136 + {false, State} -> 137 + {ok, {FID0, lists:reverse(Acc)}, State}; 138 + {{QID, FState}, State} -> 139 + FID = #fid{qid = QID, state = FState, path = Path ++ [P]}, 140 + do_walk(Mod, FID, Rest, State, [QID | Acc]) 141 + end; 142 + false -> 143 + {error, io_lib:format("Not directory ~p", [Path]), State0} 144 + end. 119 145 120 - do_walk(_Mod, QID, [], State, Acc) -> 121 - {ok, {QID, lists:reverse(Acc)}, State}; 122 - do_walk(Mod, QID0, [P | Rest], State0, Acc) -> 123 - case Mod:walk(QID0, P, State0) of 124 - {false, State} when Acc =:= [] -> 125 - % Per specification walk to first entry in name list must succeed 126 - % (if any) otherwise return error. In subsequent steps we return 127 - % successful list and last succeeded QID 128 - {error, io_lib:format("Failed walk to ~p", [P]), State}; 129 - {false, State} -> 130 - {ok, {QID0, lists:reverse(Acc)}, State}; 131 - {QID, State} -> 132 - do_walk(Mod, QID, Rest, State, [QID | Acc]) 146 + open({Mod, State0}, #fid{qid = QID, path = Path, state = FState0} = FID, Mode) -> 147 + EMode = translate_mode(Mode), 148 + case Mod:open({QID, FState0}, Path, EMode, State0) of 149 + {ok, {FState, IOUnit}, State} -> 150 + {ok, {FID#fid{state = FState}, IOUnit}, {Mod, State}}; 151 + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} 133 152 end. 134 153 135 - open({Mod, State}, QID, Mode) -> 136 - ?if_supported(Mod:open(QID, Mode, State)). 154 + translate_mode(Mode) when Mode >= 16#10 -> 155 + [trunc | translate_mode(Mode band 16#EF)]; 156 + translate_mode(0) -> [read]; 157 + translate_mode(1) -> [write]; 158 + translate_mode(2) -> [append]; 159 + translate_mode(3) -> [exec]. 137 160 138 - create({Mod, State}, QID, Name, Perm, Mode) -> 139 - ?if_supported(Mod:create(QID, Name, Perm, Mode, State)). 161 + create({Mod, State}, #fid{qid = QID, path = Path, state = FState}, Name, Perm, Mode) -> 162 + ?if_supported(Mod:create({QID, FState}, Path, Name, Perm, Mode, State)). 140 163 141 - read({Mod, State}, QID, Offset, Length) -> 142 - ?if_supported(Mod:read(QID, Offset, Length, State)). 164 + read({Mod, State0}, #fid{qid = QID, path = Path, state = FState0} = FID, Offset, Length) -> 165 + case Mod:read({QID, FState0}, Path, Offset, Length, State0) of 166 + {ok, {FState, Data}, State} -> {ok, {FID#fid{state = FState}, Data}, {Mod, State}}; 167 + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} 168 + end. 143 169 144 - write({Mod, State}, QID, Offset, Data) -> 145 - ?if_supported(Mod:write(QID, Offset, Data, State)). 170 + write({Mod, State0}, #fid{qid = QID, path = Path, state = FState0} = FID, Offset, Data) -> 171 + case Mod:write({QID, FState0}, Path, Offset, Data, State0) of 172 + {ok, {FState, Len}, State} -> {ok, {FID#fid{state = FState}, Len}, {Mod, State}}; 173 + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} 174 + end. 146 175 147 - clunk({Mod, State0}, QID) -> 176 + clunk({Mod, State0}, #fid{qid = QID, path = Path, state = FState}) -> 148 177 case erlang:function_exported(Mod, clunk, 3) of 149 178 true -> 150 - maybe 151 - {ok, State} ?= Mod:clunk(QID, State0), 152 - {ok, {Mod, State}} 153 - else 154 - {error, Reason, StateE} -> 155 - {error, Reason, {Mod, StateE}} 179 + case Mod:clunk({QID, FState}, Path, State0) of 180 + {ok, State} -> {ok, {Mod, State}}; 181 + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} 156 182 end; 157 183 false -> {ok, {Mod, State0}} 158 184 end. 159 185 160 - remove({Mod, State}, QID) -> 161 - ?if_supported(Mod:remove(QID, State)). 186 + remove({Mod, State0}, #fid{qid = QID, path = Path, state = FState}) -> 187 + case Mod:remove({QID, FState}, Path, State0) of 188 + {ok, State} -> {ok, {Mod, State}}; 189 + {error, Reason, State} -> {error, Reason, {Mod, State}} 190 + end. 162 191 163 - stat({Mod, State}, QID) -> 164 - ?if_supported(Mod:stat(QID, State)). 192 + stat({Mod, State0}, #fid{qid = QID, path = Path, state = FState0}) -> 193 + case Mod:stat({QID, FState0}, Path, State0) of 194 + {ok, Stat, State} -> {ok, Stat, {Mod, State}}; 195 + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} 196 + end. 165 197 166 - wstat({Mod, State}, QID, Stat) -> 167 - ?if_supported(Mod:wstat(QID, Stat, State)). 198 + wstat({Mod, State0}, #fid{qid = QID, path = Path, state = FState}, Stat) -> 199 + case Mod:wstat({QID, FState}, Path, Stat, State0) of 200 + {ok, State} -> {ok, {Mod, State}}; 201 + {error, Reason, State} -> {error, Reason, {Mod, State}} 202 + end.
+3
src/e9p_internal.hrl
··· 53 53 -define(Twstat, 126). 54 54 -define(Rwstat, 127). 55 55 56 + -record(qid, {type, version, path}). 57 + -record(fid, {qid, path, state}). 58 + 56 59 -record(tversion, {max_packet_size, version}). 57 60 -record(rversion, {max_packet_size, version}). 58 61
+12 -3
src/e9p_msg.erl
··· 162 162 {ok, #tread{fid = FID, offset = Offset, len = Len}}; 163 163 do_parse(?Rread, <<Count:4/?int, Data:Count/?int>>) -> 164 164 {ok, #rread{data = Data}}; 165 + do_parse(?Twrite, <<FID:4/?int, Offset:8/?int, Len:4/?int, Data:Len/binary>>) -> 166 + {ok, #twrite{fid = FID, offset = Offset, data = Data}}; 167 + do_parse(?Rwrite, <<Len:4/?int>>) -> 168 + {ok, #rwrite{len = Len}}; 165 169 166 170 do_parse(Type, Data) -> 167 171 {error, {invalid_message, Type, Data}}. ··· 273 277 {?Tread, <<FID:4/?int, Offset:8/?int, Len:4/?int>>}; 274 278 do_encode(#rread{data = Data}) -> 275 279 Len = iolist_size(Data), 276 - {?Rread, [<<Len:4/?int>> | Data]}. 280 + {?Rread, [<<Len:4/?int>> | Data]}; 281 + do_encode(#twrite{fid = FID, offset = Offset, data = Data}) -> 282 + Len = iolist_size(Data), 283 + {?Twrite, [<<FID:4/?int, Offset:8/?int, Len:4/?int>>, Data]}; 284 + do_encode(#rwrite{len = Len}) -> 285 + {?Rwrite, [<<Len:4/?int>>]}. 277 286 278 287 encode_stat(Stat) -> 279 288 #{ ··· 326 335 [<<Len:?len>>, Data]. 327 336 328 337 binary_to_qid(<<Type:1/?int, Version:4/?int, Path:8/?int>>) -> 329 - #{type => Type, version => Version, path => Path, state => []}. 338 + #qid{type = Type, version = Version, path = Path}. 330 339 331 - qid_to_binary(#{type := Type, version := Version, path := Path}) -> 340 + qid_to_binary(#qid{type = Type, version = Version, path = Path}) -> 332 341 <<Type:1/?int, Version:4/?int, Path:8/?int>>. 333 342 334 343 time_to_encoded_sec(Sec) when is_integer(Sec) -> <<Sec:4/?int>>;
+5 -8
src/e9p_server.erl
··· 22 22 handler 23 23 }). 24 24 25 - -record(fid, {qid, path, state}). 26 - 27 25 start_link(Port, Handler) -> 28 26 proc_lib:start_link(?MODULE, setup_acceptor, [self(), Port, Handler]). 29 27 ··· 60 58 loop(#state{socket = Sock} = State) -> 61 59 case e9p_transport:read(Sock) of 62 60 {ok, Tag, Data} -> 63 - ?LOG_DEBUG(#{msg => Data}), 64 61 try handle_message(Data, State#state.fids, State#state.handler) of 65 62 {ok, Reply, FIDs, Handler} -> 66 63 e9p_transport:send(Sock, Tag, Reply), ··· 92 89 maybe 93 90 {ok, QID, Handler} ?= e9p_fs:root(Handler0, UName, AName), 94 91 NFIDs = FIDs#{FID => QID}, 95 - {ok, #rattach{qid = QID}, NFIDs, Handler} 92 + {ok, #rattach{qid = QID#fid.qid}, NFIDs, Handler} 96 93 end; 97 94 98 95 handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) -> ··· 106 103 maybe 107 104 {ok, QID} ?= get_qid(FIDs, FID), 108 105 {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID, Mode), 109 - {ok, #ropen{qid = QID, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} 106 + {ok, #ropen{qid = QID#fid.qid, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} 110 107 end; 111 108 handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) -> 112 109 maybe 113 110 {ok, QID} ?= get_qid(FIDs, FID), 114 111 {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:create(Handler0, QID, Name, Perm, Mode), 115 - {ok, #rcreate{qid = NewQID, io_unit = IOUnit}, FIDs, Handler} 112 + {ok, #rcreate{qid = NewQID#fid.qid, io_unit = IOUnit}, FIDs, Handler} 116 113 end; 117 114 118 115 handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> ··· 124 121 handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) -> 125 122 maybe 126 123 {ok, QID} ?= get_qid(FIDs, FID), 127 - {ok, {NQID, Data}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), 128 - {ok, #rread{data = Data}, FIDs#{FID => NQID}, Handler} 124 + {ok, {NQID, Len}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), 125 + {ok, #rwrite{len = Len}, FIDs#{FID => NQID}, Handler} 129 126 end; 130 127 131 128 handle_message(#tclunk{fid = FID}, FIDs, Handler0) ->
+91 -47
src/e9p_unfs.erl
··· 9 9 -include_lib("kernel/include/logger.hrl"). 10 10 -include_lib("kernel/include/file.hrl"). 11 11 12 - -export([init/1, root/3, walk/3, stat/2, open/3, read/4, clunk/2]). 13 - 14 - -doc """ 15 - Create QID for given path. 16 - """. 17 - qid(Path) -> 18 - case file:read_file_info(Path, [{time, posix}]) of 19 - {ok, #file_info{type = Type, inode = Inode}} -> 20 - NQid = e9p:make_qid(Type, 0, Inode, {Path, []}), 21 - {ok, NQid}; 22 - {error, _} = Error -> Error 23 - end. 12 + -export([init/1, root/3, walk/4, stat/3, open/4, read/5, clunk/2, create/6, 13 + write/5, remove/3, wstat/4]). 24 14 25 15 -doc """ 26 16 Create QID and Stat data for given path. 27 17 """. 28 - qid_stat(Root, Path) -> 29 - case file:read_file_info(Path, [{time, posix}]) of 18 + qid(Root, Path) -> 19 + FullPath = filename:join([Root] ++ Path), 20 + case file:read_file_info(FullPath, [{time, posix}]) of 30 21 {ok, #file_info{type = Type, inode = Inode} = FI} -> 31 - NQid = e9p:make_qid(Type, 0, Inode, {Path, []}), 32 - Stat = file_info_to_stat(Root, NQid, FI), 33 - {ok, NQid, Stat}; 22 + QID = e9p:make_qid(Type, 0, Inode), 23 + Stat = file_info_to_stat(Path, QID, FI), 24 + {ok, QID, Stat}; 34 25 {error, _} = Error -> Error 35 26 end. 36 27 ··· 40 31 root(UName, AName, #{root := Root} = State) -> 41 32 ?LOG_INFO(#{uname => UName, aname => AName}), 42 33 maybe 43 - {ok, Qid} ?= qid(Root), 44 - {ok, Qid, State} 34 + {ok, Qid, _Stat} ?= qid(Root, []), 35 + {ok, {Qid, []}, State} 45 36 end. 46 37 47 - walk(#{state := {Root, _}}, ~"..", #{root := Root} = State) -> 38 + walk(_QID, [], ~"..", State) -> 48 39 {false, State}; 49 - walk(#{state := {Path, _}}, ~"..", State) -> 50 - Next = filename:dirname(Path), 51 - case qid(Next) of 52 - {ok, NQid} -> {NQid, State}; 40 + walk(_QID, Path, ~"..", #{root := Root} = State) -> 41 + case qid(Root, lists:droplast(Path)) of 42 + {ok, NQid, _Stat} -> {{NQid, []}, State}; 53 43 {error, _} -> {false, State} 54 44 end; 55 - walk(#{state := {Path, _}}, File, State) -> 56 - Next = filename:join(Path, File), 57 - case qid(Next) of 58 - {ok, NQid} -> {NQid, State}; 45 + walk(_QID, Path, File, #{root := Root} = State) -> 46 + case qid(Root, Path ++ [File]) of 47 + {ok, NQid, _Stat} -> {{NQid, []}, State}; 59 48 {error, _} -> {false, State} 60 49 end. 61 50 62 - stat(#{state := {Path, _}} = QID, #{root := Root} = State) -> 63 - case file:read_file_info(Path, [{time, posix}]) of 51 + stat({QID, _}, Path, #{root := Root} = State) -> 52 + FullPath = filename:join([Root] ++ Path), 53 + case file:read_file_info(FullPath, [{time, posix}]) of 64 54 {ok, FileInfo} -> 65 - Stat = file_info_to_stat(Root, QID, FileInfo), 55 + Stat = file_info_to_stat(Path, QID, FileInfo), 66 56 {ok, Stat, State}; 67 57 {error, Error} -> 68 58 {error, Error, State} 69 59 end. 70 60 71 - open(#{state := {Path, []}} = QID, _Mode, State) -> 61 + open({QID, []}, Path, Mode, #{root := Root} = State) -> 62 + FullPath = filename:join([Root] ++ Path), 72 63 QS = case e9p:is_type(QID, directory) of 73 64 true -> 74 - {ok, List} = file:list_dir(Path), 65 + {ok, List} = file:list_dir(FullPath), 75 66 {dir, List}; 76 67 false -> 77 - {ok, FD} = file:open(Path, [raw, read, binary]), 68 + {Trunc, Opts} = translate_mode(Mode), 69 + {ok, FD} = file:open(FullPath, [raw, binary | Opts]), 70 + if Trunc -> file:truncate(FD); true -> ok end, 78 71 {regular, FD} 79 72 end, 80 - NQID = QID#{state => {Path, QS}}, 81 - {ok, {NQID, 0}, State}. 73 + {ok, {QS, 0}, State}. 82 74 83 - read(#{state := {_, {regular, FD}}} = QID, Offset, Len, State) -> 75 + create(_QID, _Path, _Name, _Perm, _Mode, State) -> 76 + {error, "Unsupported", State}. 77 + 78 + remove({QID, _} = FID, Path, #{root := Root} = State0) -> 79 + FullPath = filename:join([Root] ++ Path), 80 + {ok, State} = clunk(FID, State0), 81 + case case e9p:is_type(QID, directory) of 82 + true -> file:del_dir(FullPath); 83 + false -> file:delete(FullPath) 84 + end of 85 + ok -> {ok, State}; 86 + {error, Reason} -> 87 + {error, io_lib:format("Failed to remove path: ~p", [Reason]), State} 88 + end. 89 + 90 + translate_mode([trunc | Rest]) -> 91 + {_, Mode} = translate_mode(Rest), 92 + {true, Mode}; 93 + translate_mode([read]) -> {false, [read]}; 94 + translate_mode([write]) -> {false, [read, write]}; 95 + translate_mode([append]) -> {false, [read, write]}; 96 + translate_mode([exec]) -> {false, [read]}. 97 + 98 + read({_QID, {regular, FD}}, _Path, Offset, Len, State) -> 84 99 case file:pread(FD, Offset, Len) of 85 - {ok, Data} -> {ok, {QID, Data}, State}; 86 - eof -> {ok, {QID, []}, State}; 100 + {ok, Data} -> {ok, {{regular, FD}, Data}, State}; 101 + eof -> {ok, {{regular, FD}, []}, State}; 87 102 {error, Err} -> {error, Err, State} 88 103 end; 89 - read(#{state := {Path, {dir, List}}} = QID, _Offset, Len, #{root := Root} = State) -> 104 + read({_QID, {dir, List}}, Path, _Offset, Len, #{root := Root} = State) -> 90 105 {Remaining, Data} = readdir(Root, Path, List, Len, []), 91 - {ok, {QID#{state => {Path, {dir, Remaining}}}, Data}, State}. 106 + {ok, {{dir, Remaining}, Data}, State}. 92 107 93 108 readdir(_Root, _Path, List, 0, Acc) -> {List, Acc}; 94 109 readdir(_Root, _Path, [], _Len, Acc) -> {[], Acc}; 95 110 readdir(Root, Path, [Next | Rest], Len, Acc) -> 96 - {ok, _QID, Stat} = qid_stat(Root, filename:join(Path, Next)), 111 + {ok, _QID, Stat} = qid(Root, Path ++ [Next]), 97 112 Encoded = e9p_msg:encode_stat(Stat), 98 113 Size = iolist_size(Encoded), 99 114 if ··· 101 116 true -> readdir(Root, Path, Rest, Len - Size, [Encoded | Acc]) 102 117 end. 103 118 104 - clunk(#{state := {_Path, {refular, FD}}}, State) -> 119 + write({_QID, {regular, FD}}, _Path, Offset, Data, State) -> 120 + case file:pwrite(FD, Offset, Data) of 121 + ok -> {ok, {{regular, FD}, iolist_size(Data)}, State}; 122 + {error, Err} -> {error, io_lib:format("Write error ~p", [Err]), State} 123 + end. 124 + 125 + clunk({_, {regular, FD}}, State) -> 105 126 ok = file:close(FD), 106 127 {ok, State}; 107 128 clunk(_QID, State) -> 108 129 {ok, State}. 109 130 131 + wstat(_QID, Path, Stat, #{root := Root} = State) -> 132 + FileInfo = stat_to_file_info(Stat), 133 + FullPath = filename:join([Root] ++ Path), 134 + 135 + case file:write_file_info(FullPath, FileInfo, [{time, posix}]) of 136 + ok -> {ok, State}; 137 + {error, Reason} -> 138 + {error, io_lib:format("Couldn't write file stat: ~p", [Reason]), 139 + State} 140 + end. 141 + 110 142 file_info_to_stat( 111 - Root, 112 - #{state := {Path, _}} = QID, 143 + Path, 144 + QID, 113 145 #file_info{ 114 146 size = Len, 115 147 atime = Atime, ··· 117 149 mode = Mode 118 150 }) -> 119 151 Name = if 120 - Root == Path -> ~"/"; 121 - true -> filename:basename(Path) 152 + Path == [] -> ~"/"; 153 + true -> lists:last(Path) 122 154 end, 123 155 #{ 124 156 qid => QID, ··· 128 160 length => Len, 129 161 name => Name 130 162 }. 163 + 164 + stat_to_file_info(Stat) -> 165 + #{ 166 + mode := Mode, 167 + atime := Atime, 168 + mtime := Mtime 169 + } = Stat, 170 + #file_info{ 171 + mode = Mode, 172 + atime = Atime, 173 + mtime = Mtime 174 + }.