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

Test sysfs implementation

hauleth.dev 6b4d97bd 4dd8b76d

verified
+165 -11
+8 -8
flake.nix
··· 34 34 devenv.shells.default = { 35 35 languages.erlang.enable = true; 36 36 37 - packages = [ kamid ]; 37 + packages = [ kamid pkgs._9pfs ]; 38 38 39 - env.ERL_FLAGS = '' 40 - -kernel logger_level debug 41 - -kernel logger '[ 42 - {filters, log, [{no_progress, {fun logger_filters:progress/2, stop}}]} 43 - ]' 44 - -kernel shell_history enabled 45 - ''; 39 + # env.ERL_FLAGS = '' 40 + # -kernel logger_level debug 41 + # -kernel logger '[ 42 + # {filters, log, [{no_progress, {fun logger_filters:progress/2, stop}}]} 43 + # ]' 44 + # -kernel shell_history enabled 45 + # ''; 46 46 }; 47 47 }; 48 48 };
+4 -1
rebar.config
··· 9 9 10 10 {project_plugins, [ 11 11 rebar3_ex_doc, 12 - rebar3_proper 12 + rebar3_proper, 13 + covertool 13 14 ]}. 14 15 15 16 {profiles, [{test, [ 16 17 {deps, [proper]}, 17 18 {erl_opts, [nowarn_export_all]} 18 19 ]}]}. 20 + 21 + {ct_opts, [{create_priv_dir, auto_per_run}]}.
+7 -2
src/e9p_server.erl
··· 8 8 9 9 -include_lib("kernel/include/logger.hrl"). 10 10 11 - -export([start_link/2, 12 - setup_acceptor/3, 11 + -export([start/2, 12 + start_link/2]). 13 + 14 + -export([setup_acceptor/3, 13 15 accept_loop/2, 14 16 loop/1 15 17 ]). ··· 21 23 fids = #{}, 22 24 handler 23 25 }). 26 + 27 + start(Port, Handler) -> 28 + proc_lib:start(?MODULE, setup_acceptor, [self(), Port, Handler]). 24 29 25 30 start_link(Port, Handler) -> 26 31 proc_lib:start_link(?MODULE, setup_acceptor, [self(), Port, Handler]).
+146
test/e9p_sysfs_SUITE.erl
··· 1 + % SPDX-FileCopyrightText: 2026 Łukasz Niemier <~@hauleth.dev> 2 + % 3 + % SPDX-License-Identifier: Apache-2.0 4 + 5 + -module(e9p_sysfs_SUITE). 6 + 7 + -compile(export_all). 8 + 9 + -include_lib("stdlib/include/assert.hrl"). 10 + -include_lib("common_test/include/ct.hrl"). 11 + 12 + all() -> [ 13 + can_list_mount_content, 14 + current_process_is_listed, 15 + current_process_current_function, 16 + system_info_atom_count, 17 + applications_list, 18 + application_info, 19 + application_env 20 + ]. 21 + 22 + init_per_suite(Config) -> 23 + PrivDir = ?config(priv_dir, Config), 24 + Path = filename:join(PrivDir, "sysfs"), 25 + Port = 19999, 26 + ok = file:make_dir(Path), 27 + {ok, PID} = e9p_server:start(Port, {e9p_sysfs, []}), 28 + ct:pal(Path), 29 + ct:sleep(1000), 30 + Cmd = io_lib:format("9pfs -p ~B localhost ~s", 31 + [Port, Path]), 32 + ct:pal(Cmd), 33 + _Out = os:cmd(Cmd, #{ exception_on_failure => true }), 34 + [{sysfs, PID}, {mount, Path} | Config]. 35 + 36 + end_per_suite(Config) -> 37 + PID = ?config(sysfs, Config), 38 + Mount = ?config(mount, Config), 39 + os:cmd(["umount ", Mount], #{exception_on_failure => true}), 40 + erlang:exit(PID, normal), 41 + Config. 42 + 43 + can_list_mount_content(Config) -> 44 + Mount = ?config(mount, Config), 45 + ct:pal(Mount), 46 + ?assertEqual([ 47 + "applications", 48 + "processes", 49 + "system_info" 50 + ], ls(Mount)). 51 + 52 + current_process_is_listed(Config) -> 53 + Mount = ?config(mount, Config), 54 + Path = filename:join([Mount, "processes", pid_to_list(self())]), 55 + ?assertEqual([ 56 + "current_function", 57 + "dictionary", 58 + "error_handler", 59 + "garbage_collection", 60 + "group_leader", 61 + "heap_size", 62 + "initial_call", 63 + "links", 64 + "message_queue_len", 65 + "priority", 66 + "reductions", 67 + "stack_size", 68 + "status", 69 + "total_heap_size", 70 + "trap_exit" 71 + ], ls(Path)). 72 + 73 + current_process_current_function(Config) -> 74 + Mount = ?config(mount, Config), 75 + Path = filename:join([ 76 + Mount, 77 + "processes", 78 + pid_to_list(self()), 79 + "current_function" 80 + ]), 81 + {ok, Bin} = file:read_file(Path), 82 + ?assertEqual(~"{gen,do_call,4}", Bin). 83 + 84 + system_info_atom_count(Config) -> 85 + Mount = ?config(mount, Config), 86 + Path = filename:join([ 87 + Mount, 88 + "system_info", 89 + "atom_count" 90 + ]), 91 + {ok, Bin} = file:read_file(Path), 92 + ?assertEqual(erlang:system_info(atom_count), binary_to_integer(Bin)). 93 + 94 + applications_list(Config) -> 95 + Mount = ?config(mount, Config), 96 + Path = filename:join([ 97 + Mount, 98 + "applications" 99 + ]), 100 + List = ls(Path), 101 + Apps = lists:sort(lists:map(fun({AppName, _, _}) -> atom_to_list(AppName) end, 102 + application:loaded_applications())), 103 + ?assertEqual(Apps, List). 104 + 105 + application_info(Config) -> 106 + Mount = ?config(mount, Config), 107 + Path = filename:join([ 108 + Mount, 109 + "applications", 110 + "kernel" 111 + ]), 112 + List = ls(Path), 113 + ?assertEqual([ 114 + "applications", 115 + "description", 116 + "env", 117 + "id", 118 + "included_applications", 119 + "maxP", 120 + "maxT", 121 + "mod", 122 + "modules", 123 + "optional_applications", 124 + "registered", 125 + "start_phases", 126 + "vsn" 127 + ], List). 128 + 129 + application_env(Config) -> 130 + Mount = ?config(mount, Config), 131 + Path = filename:join([ 132 + Mount, 133 + "applications", 134 + "kernel", 135 + "env" 136 + ]), 137 + {ok, [Read]} = file:consult(Path), 138 + Env = application:get_all_env(kernel), 139 + ?assertEqual(Read, Env). 140 + 141 + %% Helpers 142 + 143 + ls(Path) -> 144 + {ok, Files} = file:list_dir(Path), 145 + % Remove `.fscache` added on macOS 146 + lists:sort(Files) -- [".fscache"].