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

Implement sysfs implementation

It is basic, read-only, implementation of ERTS system information
filesystem. So far the implementation contains 3 directories:

- `processes` with list of processes containing basic process information
- `applications` with list of loaded applications as well as basic
information about these
- `system_info` with information about ERTS

Future improvements should add `memory` and `statistics` paths as well
and things like removing process directory as a way to kill process or
pseudo-file that will allow sending messages to processes.

hauleth.dev 43fffc9b 4d2f54c4

verified
+420 -57
+2
src/e9p_fs.erl
··· 124 124 125 125 do_walk(_Mod, FID, [], State, Acc) -> 126 126 {ok, {FID, lists:reverse(Acc)}, State}; 127 + do_walk(_Mod, #fid{path = []}, [~".." | _Names], State, _Acc) -> 128 + {error, "Cannot walt to root parent of root directory", State}; 127 129 do_walk(Mod, #fid{qid = QID0, path = Path, state = FState0} = FID0, [P | Rest], State0, Acc) -> 128 130 case e9p:is_type(QID0, directory) of 129 131 true ->
+7 -1
src/e9p_server.erl
··· 58 58 loop(#state{socket = Sock} = State) -> 59 59 case e9p_transport:read(Sock) of 60 60 {ok, Tag, Data} -> 61 + ?LOG_DEBUG(#{message => Data, tag => Tag}), 61 62 try handle_message(Data, State#state.fids, State#state.handler) of 62 63 {ok, Reply, FIDs, Handler} -> 63 64 e9p_transport:send(Sock, Tag, Reply), ··· 67 68 ?MODULE:loop(State#state{handler = RHandler}) 68 69 catch 69 70 C:E:S -> 71 + ?LOG_ERROR(#{ 72 + kind => C, 73 + msg => E, 74 + stacktrace => S 75 + }), 70 76 e9p_transport:send(Sock, Tag, #rerror{msg = io_lib:format("Caught ~p: ~p", [C, E])}), 71 - erlang:raise(C, E, S) 77 + ?MODULE:loop(State) 72 78 end; 73 79 {error, closed} -> 74 80 ?LOG_INFO("Connection closed"),
+342
src/e9p_sysfs.erl
··· 1 + -module(e9p_sysfs). 2 + 3 + -behaviour(e9p_fs). 4 + 5 + -include_lib("kernel/include/logger.hrl"). 6 + 7 + -export([init/1, 8 + root/3, 9 + walk/4, 10 + open/4, 11 + create/6, 12 + read/5, 13 + write/5, 14 + remove/3, 15 + stat/3, 16 + wstat/4]). 17 + 18 + init(_State) -> {ok, []}. 19 + 20 + root(_Uname, _Aname, State) -> 21 + {ok, #{qid := QID}} = stat_for([]), 22 + {ok, {QID, []}, State}. 23 + 24 + walk(_FID, Path, Name, State) -> 25 + ?LOG_DEBUG(#{path => Path, name => Name}), 26 + case stat_for(Path ++ [Name]) of 27 + {ok, #{qid := QID}} -> 28 + {{QID, []}, State}; 29 + {error, Reason} -> 30 + {error, Reason, State} 31 + end. 32 + 33 + open(_FID, [], _Mode, State) -> 34 + {ok, {[~"applications", ~"processes", ~"system_info"], 0}, State}; 35 + open(_FID, [~"system_info"], _Mode, State) -> 36 + Keys = [ 37 + ~"allocated_areas", 38 + % ~"allocator", %% TBD 39 + ~"alloc_util_allocators", 40 + % ~"allocator_sizes", %% TBD 41 + ~"cpu_topology", 42 + ~"logocal_processors", 43 + ~"logical_processors_available", 44 + ~"logical_processors_online", 45 + ~"cpu_quota", 46 + ~"update_cpu_info", 47 + ~"fullsweep_after", 48 + ~"garbage_collection", 49 + ~"heap_sizes", 50 + ~"heap_type", 51 + ~"max_heap_type", 52 + ~"message_queue_data", 53 + ~"min_heap_size", 54 + ~"min_bin_vheap_size", 55 + ~"procs", 56 + ~"atom_count", 57 + ~"atom_limit", 58 + ~"ets_count", 59 + ~"ets_limit", 60 + ~"port_count", 61 + ~"port_limit", 62 + ~"process_count", 63 + ~"process_limit", 64 + ~"end_time", 65 + ~"os_monotonic_time_source", 66 + ~"os_time_source", 67 + ~"start_time", 68 + ~"time_correlation", 69 + ~"time_offset", 70 + ~"time_warp_mode", 71 + ~"tolerant_timeofday", 72 + ~"dirty_cpu_schedulers", 73 + ~"dirty_cpu_schedulers_online", 74 + ~"dirty_io_schedulers", 75 + ~"dirty_io_schedulers_online", 76 + ~"multi_scheduling", 77 + ~"multi_scheduling_blockers", 78 + ~"normal_multi_scheduling_blockers", 79 + ~"scheduler_bind_type", 80 + ~"scheduler_bindings", 81 + % ~"scheduler_id", %% Intentionally omitted 82 + ~"schedulers", 83 + ~"schedulers_online", 84 + ~"smp_support", 85 + ~"threads", 86 + ~"thread_pool_size", 87 + ~"async_dist", 88 + ~"creation", 89 + ~"delayed_node_table_gc", 90 + ~"dist", 91 + ~"dist_buf_busy_limit", 92 + ~"dist_ctrl", 93 + ~"c_compiler_used", 94 + ~"check_io", 95 + ~"debug_compiled", 96 + ~"driver_version", 97 + ~"dynamic_trace", 98 + ~"dynamic_trace_probes", 99 + ~"emu_flavor", 100 + ~"emu_type", 101 + ~"halt_flush_timeout", 102 + ~"info", 103 + ~"kernel_poll", 104 + ~"loaded", 105 + ~"machine", 106 + ~"modified_timing_level", 107 + ~"nif_version", 108 + ~"otp_release", 109 + ~"outstanding_system_requests_limit", 110 + ~"port_parallelism", 111 + ~"system_architecture", 112 + ~"system_logger", 113 + ~"system_version", 114 + ~"trace_control_word", 115 + ~"version", 116 + ~"wordsize" 117 + ], 118 + {ok, {Keys, 0}, State}; 119 + open(_FID, [~"system_info", ~"wordsize"], _Mode, State) -> 120 + {ok, {[~"internal", ~"external"], 0}, State}; 121 + open(_FID, [~"system_info", ~"wordsize", TypeB], _Mode, State) -> 122 + Type = binary_to_atom(TypeB), 123 + Wordsize = erlang:system_info({wordsize, Type}), 124 + Data = iolist_to_binary(io_lib:format("~B", [Wordsize])), 125 + {ok, {Data, 0}, State}; 126 + open(_FID, [~"system_info", KeyB], _Mode, State) -> 127 + Key = binary_to_atom(KeyB), 128 + Val = erlang:system_info(Key), 129 + Data = iolist_to_binary(io_lib:format("~p", [Val])), 130 + {ok, {Data, 0}, State}; 131 + %% ===== Processes ===== 132 + open(_FID, [~"processes"], _Mode, State) -> 133 + Processes = lists:map(fun pid_to_list/1, erlang:processes()), 134 + {ok, {Processes, 0}, State}; 135 + open(_FID, [~"processes", _PID], _Mode, State) -> 136 + Keys = [ 137 + ~"current_function", 138 + ~"initial_call", 139 + ~"status", 140 + ~"message_queue_len", 141 + ~"links", 142 + ~"dictionary", 143 + ~"trap_exit", 144 + ~"error_handler", 145 + ~"priority", 146 + ~"group_leader", 147 + ~"total_heap_size", 148 + ~"heap_size", 149 + ~"stack_size", 150 + ~"reductions", 151 + ~"garbage_collection" 152 + ], 153 + {ok, {Keys, 0}, State}; 154 + open(_FID, [~"processes", PIDB, KeyB], _Mode, State) -> 155 + PIDL = binary_to_list(PIDB), 156 + PID = list_to_pid(PIDL), 157 + Key = binary_to_existing_atom(KeyB), 158 + case erlang:process_info(PID, Key) of 159 + {Key, Val} -> 160 + Data = iolist_to_binary(io_lib:format("~p", [Val])), 161 + {ok, {Data, 0}, State}; 162 + [] -> {ok, {[], 0}, State}; 163 + undefined -> 164 + {error, "No such file", State} 165 + end; 166 + %% ===== Applications ===== 167 + open(_FID, [~"applications"], _Mode, State) -> 168 + AllApps = lists:map(fun({Name, _, _}) when is_atom(Name) -> erlang:atom_to_binary(Name) end, 169 + application:loaded_applications()), 170 + {ok, {AllApps, 0}, State}; 171 + open(_FID, [~"applications", Name], _Mode, State) -> 172 + Atom = binary_to_existing_atom(Name), 173 + {ok, AppKeys} = application:get_all_key(Atom), 174 + Keys = proplists:get_keys(AppKeys), 175 + Files = lists:map(fun erlang:atom_to_binary/1, Keys), 176 + {ok, {Files, 0}, State}; 177 + open(_FID, [~"applications", Name, ~"env"], _Mode, State) -> 178 + Atom = binary_to_existing_atom(Name), 179 + AllEnv = application:get_all_env(Atom), 180 + Data = iolist_to_binary(io_lib:format("~p", [AllEnv])), 181 + {ok, {Data, 0}, State}; 182 + open(_FID, [~"applications", NameB, KeyB], _Mode, State) -> 183 + Name = binary_to_existing_atom(NameB), 184 + Key = binary_to_existing_atom(KeyB), 185 + {ok, Val} = application:get_key(Name, Key), 186 + Data = iolist_to_binary(io_lib:format("~p", [Val])), 187 + {ok, {Data, 0}, State}. 188 + 189 + create(_FID, _Path, _Name, _Perm, _Mode, State) -> 190 + {error, "Not supported", State}. 191 + 192 + read({QID, Data}, Path, Offset, Length, State) -> 193 + case e9p:is_type(QID, directory) of 194 + true -> readdir(Data, Path, Offset, Length, State); 195 + false -> readfile(Data, Path, Offset, Length, State) 196 + end. 197 + 198 + readdir(Data, Path, Offset, Length, State) -> 199 + Encoded = lists:map(fun(Entry) -> 200 + {ok, Stat} = stat_for(Path ++ [Entry]), 201 + e9p_msg:encode_stat(Stat) 202 + end, Data), 203 + Bin = iolist_to_binary(Encoded), 204 + {ok, {Data, chunk(Bin, Offset, Length)}, State}. 205 + 206 + readfile(Data, _Path, Offset, Length, State) when is_integer(Length) -> 207 + {ok, {Data, chunk(Data, Offset, Length)}, State}. 208 + 209 + chunk(Data, Offset, Length) 210 + when is_binary(Data), is_integer(Offset), is_integer(Length) -> 211 + if 212 + Offset =< byte_size(Data) -> 213 + Len = min(Length, byte_size(Data) - Offset), 214 + binary_part(Data, Offset, Len); 215 + true -> ~"" 216 + end. 217 + 218 + write(_FID, _Path, _Offset, _Data, State) -> 219 + {error, "Unimplemented", State}. 220 + 221 + remove(_FID, _Path, State) -> 222 + {error, "Unimplemented", State}. 223 + 224 + stat(_FID, Path, State) -> 225 + case stat_for(Path) of 226 + {ok, Stat} -> {ok, Stat, State}; 227 + {error, Reason} -> {error, Reason, State} 228 + end. 229 + 230 + wstat(_FID, _Path, _Stat, State) -> 231 + {error, "Not supported", State}. 232 + 233 + stat_for([]) -> 234 + {ok, #{ 235 + qid => e9p:make_qid(directory, 0, 0), 236 + name => ~"/", 237 + mode => 8#555, 238 + length => 0 239 + }}; 240 + stat_for([~"processes"]) -> 241 + {ok, #{ 242 + qid => e9p:make_qid(directory, 0, 1), 243 + name => ~"processes", 244 + mode => 8#555, 245 + length => 0 246 + }}; 247 + stat_for([~"processes", PID]) -> 248 + Hash = erlang:phash2(PID, 16#FFFF), 249 + <<Value:64>> = <<16#1, 0:24, Hash:32>>, 250 + {ok, #{ 251 + qid => e9p:make_qid(directory, 0, Value), 252 + name => PID, 253 + mode => 8#555, 254 + length => 0 255 + }}; 256 + stat_for([~"processes", PID, Key]) -> 257 + Hash = erlang:phash2(PID, 16#FFFF), 258 + KeyH = erlang:phash2(Key, 16#FFF), 259 + <<Value:64>> = <<16#1, KeyH:24, Hash:32>>, 260 + {ok, #{ 261 + qid => e9p:make_qid(regular, 0, Value), 262 + name => Key, 263 + mode => 8#555, 264 + length => 0 265 + }}; 266 + stat_for([~"applications"]) -> 267 + {ok, #{ 268 + qid => e9p:make_qid(directory, 0, 2), 269 + name => ~"applications", 270 + mode => 8#555, 271 + length => 0 272 + }}; 273 + stat_for([~"applications", Name]) -> 274 + Atom = binary_to_existing_atom(Name), 275 + case application:get_key(Atom, vsn) of 276 + undefined -> {error, "Not exist"}; 277 + {ok, _Vsn} -> 278 + Hash = erlang:phash2(Name, 16#FFFF), 279 + <<Value:64>> = <<16#2, 0:24, Hash:32>>, 280 + {ok, 281 + #{ 282 + qid => e9p:make_qid(directory, 0, Value), 283 + name => Name, 284 + mode => 8#555, 285 + length => 0 286 + }} 287 + end; 288 + stat_for([~"applications", Name, Key]) -> 289 + Hash = erlang:phash2(Name, 16#FFFF), 290 + KeyH = erlang:phash2(Key, 16#FFF), 291 + <<Value:64>> = <<16#2, KeyH:24, Hash:32>>, 292 + {ok, #{ 293 + qid => e9p:make_qid(regular, 0, Value), 294 + name => Key, 295 + mode => 8#444, 296 + length => 0 297 + }}; 298 + stat_for([~"system_info"]) -> 299 + {ok, #{ 300 + qid => e9p:make_qid(directory, 0, 3), 301 + name => ~"system_info", 302 + mode => 8#555, 303 + length => 0 304 + }}; 305 + stat_for([~"system_info", ~"wordsize"]) -> 306 + Hash = erlang:phash2(~"wordsize", 16#FFFF), 307 + <<Value:64>> = <<16#2, 0:24, Hash:32>>, 308 + {ok, #{ 309 + qid => e9p:make_qid(directory, 0, Value), 310 + name => ~"wordsize", 311 + mode => 8#555, 312 + length => 0 313 + }}; 314 + stat_for([~"system_info", ~"wordsize", ~"internal"]) -> 315 + Hash = erlang:phash2(~"wordsize", 16#FFFF), 316 + <<Value:64>> = <<16#2, 1:24, Hash:32>>, 317 + {ok, #{ 318 + qid => e9p:make_qid(regular, 0, Value), 319 + name => ~"internal", 320 + mode => 8#444, 321 + length => 0 322 + }}; 323 + stat_for([~"system_info", ~"wordsize", ~"external"]) -> 324 + Hash = erlang:phash2(~"wordsize", 16#FFFF), 325 + <<Value:64>> = <<16#2, 2:24, Hash:32>>, 326 + {ok, #{ 327 + qid => e9p:make_qid(regular, 0, Value), 328 + name => ~"external", 329 + mode => 8#444, 330 + length => 0 331 + }}; 332 + stat_for([~"system_info", Name]) -> 333 + Hash = erlang:phash2(Name, 16#FFFF), 334 + <<Value:64>> = <<16#2, 0:24, Hash:32>>, 335 + {ok, #{ 336 + qid => e9p:make_qid(regular, 0, Value), 337 + name => Name, 338 + mode => 8#444, 339 + length => 0 340 + }}; 341 + stat_for(_Path) -> 342 + {error, "Not exist"}.
+69 -56
src/e9p_unfs.erl
··· 4 4 5 5 -module(e9p_unfs). 6 6 7 + -moduledoc """ 8 + Expose Unix Filesystem as 9p2000 mount 9 + """. 10 + 7 11 -behaviour(e9p_fs). 8 12 9 13 -include_lib("kernel/include/logger.hrl"). ··· 12 16 -export([init/1, root/3, walk/4, stat/3, open/4, read/5, clunk/2, create/6, 13 17 write/5, remove/3, wstat/4]). 14 18 15 - -doc """ 16 - Create QID and Stat data for given path. 17 - """. 19 + % Create QID and Stat data for given path. 18 20 qid(Root, Path) -> 19 21 FullPath = filename:join([Root] ++ Path), 20 22 case file:read_file_info(FullPath, [{time, posix}]) of ··· 25 27 {error, _} = Error -> Error 26 28 end. 27 29 30 + file_info_to_stat( 31 + Path, 32 + QID, 33 + #file_info{ 34 + size = Len, 35 + atime = Atime, 36 + mtime = Mtime, 37 + mode = Mode 38 + }) -> 39 + Name = if 40 + Path == [] -> ~"/"; 41 + true -> lists:last(Path) 42 + end, 43 + #{ 44 + qid => QID, 45 + mode => Mode, 46 + atime => Atime, 47 + mtime => Mtime, 48 + length => Len, 49 + name => Name 50 + }. 51 + 52 + stat_to_file_info(Stat) -> 53 + #{ 54 + mode := Mode, 55 + atime := Atime, 56 + mtime := Mtime 57 + } = Stat, 58 + #file_info{ 59 + mode = Mode, 60 + atime = Atime, 61 + mtime = Mtime 62 + }. 63 + 64 + %% ====== Filesystem handlers ====== 65 + 66 + -doc false. 28 67 init(#{path := Path}) -> 29 68 {ok, #{root => unicode:characters_to_binary(Path)}}. 30 69 70 + -doc false. 31 71 root(UName, AName, #{root := Root} = State) -> 32 72 ?LOG_INFO(#{uname => UName, aname => AName}), 33 73 maybe ··· 35 75 {ok, {Qid, []}, State} 36 76 end. 37 77 38 - walk(_QID, [], ~"..", State) -> 39 - {false, State}; 78 + -doc false. 40 79 walk(_QID, Path, ~"..", #{root := Root} = State) -> 41 80 case qid(Root, lists:droplast(Path)) of 42 81 {ok, NQid, _Stat} -> {{NQid, []}, State}; ··· 48 87 {error, _} -> {false, State} 49 88 end. 50 89 90 + -doc false. 51 91 stat({QID, _}, Path, #{root := Root} = State) -> 52 92 FullPath = filename:join([Root] ++ Path), 53 93 case file:read_file_info(FullPath, [{time, posix}]) of ··· 58 98 {error, Error, State} 59 99 end. 60 100 101 + -doc false. 102 + wstat(_QID, Path, Stat, #{root := Root} = State) -> 103 + FileInfo = stat_to_file_info(Stat), 104 + FullPath = filename:join([Root] ++ Path), 105 + 106 + case file:write_file_info(FullPath, FileInfo, [{time, posix}]) of 107 + ok -> {ok, State}; 108 + {error, Reason} -> 109 + {error, io_lib:format("Couldn't write file stat: ~p", [Reason]), 110 + State} 111 + end. 112 + 113 + -doc false. 61 114 open({QID, []}, Path, Mode, #{root := Root} = State) -> 62 115 FullPath = filename:join([Root] ++ Path), 63 116 QS = case e9p:is_type(QID, directory) of ··· 72 125 end, 73 126 {ok, {QS, 0}, State}. 74 127 128 + -doc false. 129 + clunk({_, {regular, FD}}, State) -> 130 + ok = file:close(FD), 131 + {ok, State}; 132 + clunk(_QID, State) -> 133 + {ok, State}. 134 + 135 + -doc false. 75 136 create(_QID, _Path, _Name, _Perm, _Mode, State) -> 76 137 {error, "Unsupported", State}. 77 138 139 + -doc false. 78 140 remove({QID, _} = FID, Path, #{root := Root} = State0) -> 79 141 FullPath = filename:join([Root] ++ Path), 80 142 {ok, State} = clunk(FID, State0), ··· 95 157 translate_mode([append]) -> {false, [read, write]}; 96 158 translate_mode([exec]) -> {false, [read]}. 97 159 160 + -doc false. 98 161 read({_QID, {regular, FD}}, _Path, Offset, Len, State) -> 99 162 case file:pread(FD, Offset, Len) of 100 163 {ok, Data} -> {ok, {{regular, FD}, Data}, State}; ··· 116 179 true -> readdir(Root, Path, Rest, Len - Size, [Encoded | Acc]) 117 180 end. 118 181 182 + -doc false. 119 183 write({_QID, {regular, FD}}, _Path, Offset, Data, State) -> 120 184 case file:pwrite(FD, Offset, Data) of 121 185 ok -> {ok, {{regular, FD}, iolist_size(Data)}, State}; 122 186 {error, Err} -> {error, io_lib:format("Write error ~p", [Err]), State} 123 187 end. 124 - 125 - clunk({_, {regular, FD}}, State) -> 126 - ok = file:close(FD), 127 - {ok, State}; 128 - clunk(_QID, State) -> 129 - {ok, State}. 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 - 142 - file_info_to_stat( 143 - Path, 144 - QID, 145 - #file_info{ 146 - size = Len, 147 - atime = Atime, 148 - mtime = Mtime, 149 - mode = Mode 150 - }) -> 151 - Name = if 152 - Path == [] -> ~"/"; 153 - true -> lists:last(Path) 154 - end, 155 - #{ 156 - qid => QID, 157 - mode => Mode, 158 - atime => Atime, 159 - mtime => Mtime, 160 - length => Len, 161 - name => Name 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 - }.