Pure Erlang implementation of 9p2000 protocol
filesystem fs 9p2000 erlang 9p
at master 351 lines 11 kB view raw
1% SPDX-FileCopyrightText: 2026 Łukasz Niemier <~@hauleth.dev> 2% 3% SPDX-License-Identifier: Apache-2.0 4 5-module(e9p_sysfs). 6 7-behaviour(e9p_fs). 8 9-include_lib("kernel/include/logger.hrl"). 10 11-export([init/1, 12 root/3, 13 walk/4, 14 open/4, 15 create/6, 16 read/5, 17 write/5, 18 remove/3, 19 stat/3, 20 wstat/4]). 21 22init(_State) -> {ok, []}. 23 24root(_Uname, _Aname, State) -> 25 {ok, #{qid := QID}} = stat_for([]), 26 {ok, {QID, []}, State}. 27 28walk(_FID, Path, Name, State) -> 29 ?LOG_DEBUG(#{path => Path, name => Name}), 30 case stat_for(Path ++ [Name]) of 31 {ok, #{qid := QID}} -> 32 {{QID, []}, State}; 33 {error, Reason} -> 34 {error, Reason, State} 35 end. 36 37open(_FID, [], _Mode, State) -> 38 {ok, {[~"applications", ~"processes", ~"system_info"], 0}, State}; 39open(_FID, [~"system_info"], _Mode, State) -> 40 Keys = [ 41 ~"allocated_areas", 42 % ~"allocator", %% TBD 43 ~"alloc_util_allocators", 44 % ~"allocator_sizes", %% TBD 45 ~"cpu_topology", 46 ~"logical_processors", 47 ~"logical_processors_available", 48 ~"logical_processors_online", 49 ~"cpu_quota", 50 ~"update_cpu_info", 51 ~"fullsweep_after", 52 ~"garbage_collection", 53 ~"heap_sizes", 54 ~"heap_type", 55 ~"max_heap_size", 56 ~"message_queue_data", 57 ~"min_heap_size", 58 ~"min_bin_vheap_size", 59 ~"procs", 60 ~"atom_count", 61 ~"atom_limit", 62 ~"ets_count", 63 ~"ets_limit", 64 ~"port_count", 65 ~"port_limit", 66 ~"process_count", 67 ~"process_limit", 68 ~"end_time", 69 ~"os_monotonic_time_source", 70 ~"os_system_time_source", 71 ~"start_time", 72 ~"time_correction", 73 ~"time_offset", 74 ~"time_warp_mode", 75 ~"tolerant_timeofday", 76 ~"dirty_cpu_schedulers", 77 ~"dirty_cpu_schedulers_online", 78 ~"dirty_io_schedulers", 79 ~"multi_scheduling", 80 ~"multi_scheduling_blockers", 81 ~"normal_multi_scheduling_blockers", 82 ~"scheduler_bind_type", 83 ~"scheduler_bindings", 84 % ~"scheduler_id", %% Intentionally omitted 85 ~"schedulers", 86 ~"schedulers_online", 87 ~"smp_support", 88 ~"threads", 89 ~"thread_pool_size", 90 ~"async_dist", 91 ~"creation", 92 ~"delayed_node_table_gc", 93 ~"dist", 94 ~"dist_buf_busy_limit", 95 ~"dist_ctrl", 96 ~"c_compiler_used", 97 ~"check_io", 98 ~"debug_compiled", 99 ~"driver_version", 100 ~"dynamic_trace", 101 ~"dynamic_trace_probes", 102 ~"emu_flavor", 103 ~"emu_type", 104 ~"halt_flush_timeout", 105 ~"info", 106 ~"kernel_poll", 107 ~"loaded", 108 ~"machine", 109 ~"modified_timing_level", 110 ~"nif_version", 111 ~"otp_release", 112 ~"outstanding_system_requests_limit", 113 ~"port_parallelism", 114 ~"system_architecture", 115 ~"system_logger", 116 ~"system_version", 117 ~"trace_control_word", 118 ~"version", 119 ~"wordsize" 120 ], 121 {ok, {Keys, 0}, State}; 122open(_FID, [~"system_info", ~"wordsize"], _Mode, State) -> 123 {ok, {[~"internal", ~"external"], 0}, State}; 124open(_FID, [~"system_info", ~"wordsize", TypeB], _Mode, State) -> 125 Type = binary_to_atom(TypeB), 126 Wordsize = erlang:system_info({wordsize, Type}), 127 Data = iolist_to_binary(io_lib:format("~B", [Wordsize])), 128 {ok, {Data, 0}, State}; 129open(_FID, [~"system_info", KeyB], _Mode, State) -> 130 Key = binary_to_atom(KeyB), 131 Data = case erlang:system_info(Key) of 132 Val when is_binary(Val) -> Val; 133 Val -> 134 case io_lib:printable_list(Val) of 135 true -> unicode:characters_to_binary(Val); 136 false -> iolist_to_binary(io_lib:format("~p", [Val])) 137 end 138 end, 139 {ok, {Data, 0}, State}; 140%% ===== Processes ===== 141open(_FID, [~"processes"], _Mode, State) -> 142 Processes = lists:map(fun pid_to_list/1, erlang:processes()), 143 {ok, {Processes, 0}, State}; 144open(_FID, [~"processes", _PID], _Mode, State) -> 145 Keys = [ 146 ~"current_function", 147 ~"initial_call", 148 ~"status", 149 ~"message_queue_len", 150 ~"links", 151 ~"dictionary", 152 ~"trap_exit", 153 ~"error_handler", 154 ~"priority", 155 ~"group_leader", 156 ~"total_heap_size", 157 ~"heap_size", 158 ~"stack_size", 159 ~"reductions", 160 ~"garbage_collection" 161 ], 162 {ok, {Keys, 0}, State}; 163open(_FID, [~"processes", PIDB, KeyB], _Mode, State) -> 164 PIDL = binary_to_list(PIDB), 165 PID = list_to_pid(PIDL), 166 Key = binary_to_existing_atom(KeyB), 167 case erlang:process_info(PID, Key) of 168 {Key, Val} -> 169 Data = iolist_to_binary(io_lib:format("~p", [Val])), 170 {ok, {Data, 0}, State}; 171 [] -> {ok, {[], 0}, State}; 172 undefined -> 173 {error, "No such file", State} 174 end; 175%% ===== Applications ===== 176open(_FID, [~"applications"], _Mode, State) -> 177 AllApps = lists:map(fun({Name, _, _}) when is_atom(Name) -> erlang:atom_to_binary(Name) end, 178 application:loaded_applications()), 179 {ok, {AllApps, 0}, State}; 180open(_FID, [~"applications", Name], _Mode, State) -> 181 Atom = binary_to_existing_atom(Name), 182 {ok, AppKeys} = application:get_all_key(Atom), 183 Keys = proplists:get_keys(AppKeys), 184 Files = lists:map(fun erlang:atom_to_binary/1, Keys), 185 {ok, {Files, 0}, State}; 186open(_FID, [~"applications", Name, ~"env"], _Mode, State) -> 187 Atom = binary_to_existing_atom(Name), 188 AllEnv = application:get_all_env(Atom), 189 Data = iolist_to_binary(io_lib:format("%% coding: utf-8~n~n~p.", [AllEnv])), 190 {ok, {Data, 0}, State}; 191open(_FID, [~"applications", NameB, KeyB], _Mode, State) -> 192 Name = binary_to_existing_atom(NameB), 193 Key = binary_to_existing_atom(KeyB), 194 {ok, Val} = application:get_key(Name, Key), 195 Data = iolist_to_binary(io_lib:format("~p", [Val])), 196 {ok, {Data, 0}, State}. 197 198create(_FID, _Path, _Name, _Perm, _Mode, State) -> 199 {error, "Not supported", State}. 200 201read({QID, Data}, Path, Offset, Length, State) -> 202 case e9p:is_type(QID, directory) of 203 true -> readdir(Data, Path, Offset, Length, State); 204 false -> readfile(Data, Path, Offset, Length, State) 205 end. 206 207readdir(Data, Path, Offset, Length, State) -> 208 Encoded = lists:map(fun(Entry) -> 209 {ok, Stat} = stat_for(Path ++ [Entry]), 210 e9p_msg:encode_stat(Stat) 211 end, Data), 212 Bin = iolist_to_binary(Encoded), 213 {ok, {Data, chunk(Bin, Offset, Length)}, State}. 214 215readfile(Data, _Path, Offset, Length, State) when is_integer(Length) -> 216 {ok, {Data, chunk(Data, Offset, Length)}, State}. 217 218chunk(Data, Offset, Length) 219 when is_binary(Data), is_integer(Offset), is_integer(Length) -> 220 if 221 Offset =< byte_size(Data) -> 222 Len = min(Length, byte_size(Data) - Offset), 223 binary_part(Data, Offset, Len); 224 true -> ~"" 225 end. 226 227write(_FID, _Path, _Offset, _Data, State) -> 228 {error, "Unimplemented", State}. 229 230remove(_FID, _Path, State) -> 231 {error, "Unimplemented", State}. 232 233stat(_FID, Path, State) -> 234 case stat_for(Path) of 235 {ok, Stat} -> {ok, Stat, State}; 236 {error, Reason} -> {error, Reason, State} 237 end. 238 239wstat(_FID, _Path, _Stat, State) -> 240 {error, "Not supported", State}. 241 242stat_for([]) -> 243 {ok, #{ 244 qid => e9p:make_qid(directory, 0, 0), 245 name => ~"/", 246 mode => 8#555, 247 length => 0 248 }}; 249stat_for([~"processes"]) -> 250 {ok, #{ 251 qid => e9p:make_qid(directory, 0, 1), 252 name => ~"processes", 253 mode => 8#555, 254 length => 0 255 }}; 256stat_for([~"processes", PID]) -> 257 Hash = erlang:phash2(PID, 16#FFFF), 258 <<Value:64>> = <<16#1, 0:24, Hash:32>>, 259 {ok, #{ 260 qid => e9p:make_qid(directory, 0, Value), 261 name => PID, 262 mode => 8#555, 263 length => 0 264 }}; 265stat_for([~"processes", PID, Key]) -> 266 Hash = erlang:phash2(PID, 16#FFFF), 267 KeyH = erlang:phash2(Key, 16#FFF), 268 <<Value:64>> = <<16#1, KeyH:24, Hash:32>>, 269 {ok, #{ 270 qid => e9p:make_qid(regular, 0, Value), 271 name => Key, 272 mode => 8#555, 273 length => 0 274 }}; 275stat_for([~"applications"]) -> 276 {ok, #{ 277 qid => e9p:make_qid(directory, 0, 2), 278 name => ~"applications", 279 mode => 8#555, 280 length => 0 281 }}; 282stat_for([~"applications", Name]) -> 283 Atom = binary_to_existing_atom(Name), 284 case application:get_key(Atom, vsn) of 285 undefined -> {error, "Not exist"}; 286 {ok, _Vsn} -> 287 Hash = erlang:phash2(Name, 16#FFFF), 288 <<Value:64>> = <<16#2, 0:24, Hash:32>>, 289 {ok, 290 #{ 291 qid => e9p:make_qid(directory, 0, Value), 292 name => Name, 293 mode => 8#555, 294 length => 0 295 }} 296 end; 297stat_for([~"applications", Name, Key]) -> 298 Hash = erlang:phash2(Name, 16#FFFF), 299 KeyH = erlang:phash2(Key, 16#FFF), 300 <<Value:64>> = <<16#2, KeyH:24, Hash:32>>, 301 {ok, #{ 302 qid => e9p:make_qid(regular, 0, Value), 303 name => Key, 304 mode => 8#444, 305 length => 0 306 }}; 307stat_for([~"system_info"]) -> 308 {ok, #{ 309 qid => e9p:make_qid(directory, 0, 3), 310 name => ~"system_info", 311 mode => 8#555, 312 length => 0 313 }}; 314stat_for([~"system_info", ~"wordsize"]) -> 315 Hash = erlang:phash2(~"wordsize", 16#FFFF), 316 <<Value:64>> = <<16#2, 0:24, Hash:32>>, 317 {ok, #{ 318 qid => e9p:make_qid(directory, 0, Value), 319 name => ~"wordsize", 320 mode => 8#555, 321 length => 0 322 }}; 323stat_for([~"system_info", ~"wordsize", ~"internal"]) -> 324 Hash = erlang:phash2(~"wordsize", 16#FFFF), 325 <<Value:64>> = <<16#2, 1:24, Hash:32>>, 326 {ok, #{ 327 qid => e9p:make_qid(regular, 0, Value), 328 name => ~"internal", 329 mode => 8#444, 330 length => 0 331 }}; 332stat_for([~"system_info", ~"wordsize", ~"external"]) -> 333 Hash = erlang:phash2(~"wordsize", 16#FFFF), 334 <<Value:64>> = <<16#2, 2:24, Hash:32>>, 335 {ok, #{ 336 qid => e9p:make_qid(regular, 0, Value), 337 name => ~"external", 338 mode => 8#444, 339 length => 0 340 }}; 341stat_for([~"system_info", Name]) -> 342 Hash = erlang:phash2(Name, 16#FFFF), 343 <<Value:64>> = <<16#2, 0:24, Hash:32>>, 344 {ok, #{ 345 qid => e9p:make_qid(regular, 0, Value), 346 name => Name, 347 mode => 8#444, 348 length => 0 349 }}; 350stat_for(_Path) -> 351 {error, "Not exist"}.