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

Compare changes

Choose any two refs to compare.

+410 -89
+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 };
+3
nix/kamid/mode_format.patch.license
··· 1 + SPDX-FileCopyrightText: 2026 ลukasz Niemier <~@hauleth.dev> 2 + 3 + SPDX-License-Identifier: Apache-2.0
+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}]}.
+9 -8
src/e9p_msg.erl
··· 160 160 161 161 do_parse(?Tread, <<FID:4/?int, Offset:8/?int, Len:4/?int>>) -> 162 162 {ok, #tread{fid = FID, offset = Offset, len = Len}}; 163 - do_parse(?Rread, <<Count:4/?int, Data:Count/?int>>) -> 163 + do_parse(?Rread, <<Count:4/?int, Data:Count/binary>>) -> 164 164 {ok, #rread{data = Data}}; 165 165 do_parse(?Twrite, <<FID:4/?int, Offset:8/?int, Len:4/?int, Data:Len/binary>>) -> 166 166 {ok, #twrite{fid = FID, offset = Offset, data = Data}}; ··· 173 173 parse_stat(<<_Size:2/?int, 174 174 Type:2/?int, 175 175 Dev:4/?int, 176 - QID:13/binary, 176 + RawQID:13/binary, 177 177 Mode:4/?int, 178 178 Atime:4/?int, 179 179 Mtime:4/?int, ··· 183 183 GLen:?len, Gid:GLen/binary, 184 184 MULen:?len, MUid:MULen/binary>>) 185 185 -> 186 + QID = binary_to_qid(RawQID), 187 + Flags = qid_to_mode_flags(QID), 186 188 {ok, #{ 187 189 type => Type, 188 190 dev => Dev, 189 - qid => binary_to_qid(QID), 190 - mode => Mode, 191 + qid => QID, 192 + mode => Mode band (bnot Flags), 191 193 atime => Atime, 192 194 mtime => Mtime, 193 195 length => Len, ··· 308 310 gid => ~"", 309 311 muid => ~"" 310 312 }, Stat), 311 - FullMode = case e9p:is_type(QID, directory) of 312 - true -> 16#80000000 bor Mode; 313 - false -> Mode 314 - end, 313 + FullMode = qid_to_mode_flags(QID) bor Mode, 315 314 Encoded = [<< 316 315 Type:2/?int, 317 316 Dev:4/?int ··· 329 328 ELen = iolist_size(Encoded), 330 329 [<<ELen:?len>> | Encoded]. 331 330 331 + qid_to_mode_flags(#qid{type = Type}) -> 332 + (Type band 2#11100100) bsl 24. 332 333 333 334 %% ========== Utilities ========== 334 335
+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]).
+17 -8
src/e9p_sysfs.erl
··· 1 + % SPDX-FileCopyrightText: 2026 ลukasz Niemier <~@hauleth.dev> 2 + % 3 + % SPDX-License-Identifier: Apache-2.0 4 + 1 5 -module(e9p_sysfs). 2 6 3 7 -behaviour(e9p_fs). ··· 39 43 ~"alloc_util_allocators", 40 44 % ~"allocator_sizes", %% TBD 41 45 ~"cpu_topology", 42 - ~"logocal_processors", 46 + ~"logical_processors", 43 47 ~"logical_processors_available", 44 48 ~"logical_processors_online", 45 49 ~"cpu_quota", ··· 48 52 ~"garbage_collection", 49 53 ~"heap_sizes", 50 54 ~"heap_type", 51 - ~"max_heap_type", 55 + ~"max_heap_size", 52 56 ~"message_queue_data", 53 57 ~"min_heap_size", 54 58 ~"min_bin_vheap_size", ··· 63 67 ~"process_limit", 64 68 ~"end_time", 65 69 ~"os_monotonic_time_source", 66 - ~"os_time_source", 70 + ~"os_system_time_source", 67 71 ~"start_time", 68 - ~"time_correlation", 72 + ~"time_correction", 69 73 ~"time_offset", 70 74 ~"time_warp_mode", 71 75 ~"tolerant_timeofday", 72 76 ~"dirty_cpu_schedulers", 73 77 ~"dirty_cpu_schedulers_online", 74 78 ~"dirty_io_schedulers", 75 - ~"dirty_io_schedulers_online", 76 79 ~"multi_scheduling", 77 80 ~"multi_scheduling_blockers", 78 81 ~"normal_multi_scheduling_blockers", ··· 125 128 {ok, {Data, 0}, State}; 126 129 open(_FID, [~"system_info", KeyB], _Mode, State) -> 127 130 Key = binary_to_atom(KeyB), 128 - Val = erlang:system_info(Key), 129 - Data = iolist_to_binary(io_lib:format("~p", [Val])), 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, 130 139 {ok, {Data, 0}, State}; 131 140 %% ===== Processes ===== 132 141 open(_FID, [~"processes"], _Mode, State) -> ··· 177 186 open(_FID, [~"applications", Name, ~"env"], _Mode, State) -> 178 187 Atom = binary_to_existing_atom(Name), 179 188 AllEnv = application:get_all_env(Atom), 180 - Data = iolist_to_binary(io_lib:format("~p", [AllEnv])), 189 + Data = iolist_to_binary(io_lib:format("%% coding: utf-8~n~n~p.", [AllEnv])), 181 190 {ok, {Data, 0}, State}; 182 191 open(_FID, [~"applications", NameB, KeyB], _Mode, State) -> 183 192 Name = binary_to_existing_atom(NameB),
+8 -15
src/e9p_transport.erl
··· 16 16 gen_tcp:send(Socket, [<<Size:4/?int>>, Encoded]). 17 17 18 18 read(Socket) -> 19 - case gen_tcp:recv(Socket, 4) of 20 - {ok, <<Size:4/?int>>} -> 21 - case gen_tcp:recv(Socket, Size - 4) of 22 - {ok, Data} when is_binary(Data) -> 23 - case e9p_msg:parse(Data) of 24 - {ok, Tag, Msg} -> 25 - {ok, Tag, Msg}; 26 - {error, _} = Error -> 27 - Error 28 - end; 29 - {error, _} = Error -> 30 - Error 31 - end; 32 - {error, _} = Error -> 33 - Error 19 + maybe 20 + {ok, <<Size:4/?int>>} ?= gen_tcp:recv(Socket, 4), 21 + {ok, Data} ?= gen_tcp:recv(Socket, Size - 4), 22 + true = is_binary(Data), 23 + {ok, Tag, Msg} ?= e9p_msg:parse(Data), 24 + {ok, Tag, Msg} 25 + else 26 + {error, _} = Error -> Error 34 27 end. 35 28 36 29 read_stream(<<Size:4/?int, Data:(Size - 4)/binary, Rest/binary>> = Input) ->
+18 -6
src/e9p_unfs.erl
··· 19 19 % Create QID and Stat data for given path. 20 20 qid(Root, Path) -> 21 21 FullPath = filename:join([Root] ++ Path), 22 - case file:read_file_info(FullPath, [{time, posix}]) of 22 + case file:read_file_info(FullPath, [{time, posix}, raw]) of 23 23 {ok, #file_info{type = Type, inode = Inode} = FI} -> 24 24 QID = e9p:make_qid(Type, 0, Inode), 25 25 Stat = file_info_to_stat(Path, QID, FI), ··· 90 90 -doc false. 91 91 stat({QID, _}, Path, #{root := Root} = State) -> 92 92 FullPath = filename:join([Root] ++ Path), 93 - case file:read_file_info(FullPath, [{time, posix}]) of 93 + case file:read_file_info(FullPath, [{time, posix}, raw]) of 94 94 {ok, FileInfo} -> 95 95 Stat = file_info_to_stat(Path, QID, FileInfo), 96 96 {ok, Stat, State}; ··· 103 103 FileInfo = stat_to_file_info(Stat), 104 104 FullPath = filename:join([Root] ++ Path), 105 105 106 - case file:write_file_info(FullPath, FileInfo, [{time, posix}]) of 106 + case file:write_file_info(FullPath, FileInfo, [{time, posix}, raw]) of 107 107 ok -> {ok, State}; 108 108 {error, Reason} -> 109 109 {error, io_lib:format("Couldn't write file stat: ~p", [Reason]), ··· 115 115 FullPath = filename:join([Root] ++ Path), 116 116 QS = case e9p:is_type(QID, directory) of 117 117 true -> 118 - {ok, List} = file:list_dir(FullPath), 118 + % Currently `file` module do not expose raw mode for listing 119 + % file directory, so we need to call private `prim_file` module 120 + % to access such functionality. Otherwise we can encounter 121 + % deadlock. 122 + % 123 + % See: https://github.com/erlang/otp/issues/10593 124 + {ok, List} = prim_file:list_dir(FullPath), 119 125 {dir, List}; 120 126 false -> 121 127 {Trunc, Opts} = translate_mode(Mode), ··· 141 147 FullPath = filename:join([Root] ++ Path), 142 148 {ok, State} = clunk(FID, State0), 143 149 case case e9p:is_type(QID, directory) of 144 - true -> file:del_dir(FullPath); 145 - false -> file:delete(FullPath) 150 + % Currently `file` module do not expose raw mode for listing 151 + % file directory, so we need to call private `prim_file` module 152 + % to access such functionality. Otherwise we can encounter 153 + % deadlock. 154 + % 155 + % See: https://github.com/erlang/otp/issues/10593 156 + true -> prim_file:del_dir(FullPath); 157 + false -> file:delete(FullPath, [raw]) 146 158 end of 147 159 ok -> {ok, State}; 148 160 {error, Reason} ->
-17
src/e9p_utils.erl
··· 1 - % SPDX-FileCopyrightText: 2025 ลukasz Niemier <~@hauleth.dev> 2 - % 3 - % SPDX-License-Identifier: Apache-2.0 4 - 5 - -module(e9p_utils). 6 - 7 - -export([normalize_path/1]). 8 - 9 - normalize_path(List) -> normalize_path(List, []). 10 - 11 - normalize_path([], Acc) -> lists:reverse(Acc); 12 - normalize_path([Dot | Rest], Acc) 13 - when Dot =:= "." orelse Dot =:= ~"." 14 - -> 15 - normalize_path(Rest, Acc); 16 - normalize_path([P | Rest], Acc) -> 17 - normalize_path(Rest, [P | Acc]).
+28 -3
test/e9p_msg_SUITE.erl
··· 11 11 -include_lib("stdlib/include/assert.hrl"). 12 12 -include_lib("common_test/include/ct.hrl"). 13 13 14 - all() -> [stat_encode_decode, rstat_encode_decode]. 14 + all() -> [ 15 + stat_encode_decode, 16 + rstat_encode_decode, 17 + rclunk_encode_decode, 18 + rflush_encode_decode, 19 + rwstat_encode_decode, 20 + rremove_encode_decode 21 + ]. 15 22 16 23 stat_encode_decode(_Conf) -> 17 24 Stat = #{ 18 25 type => 0, 19 26 dev => 0, 20 - qid => e9p:make_qid(directory, 0, 0, []), 27 + qid => e9p:make_qid(regular, 0, 0), 21 28 mode => 0, 22 29 atime => 0, 23 30 mtime => 0, ··· 35 42 Stat = #{ 36 43 type => 0, 37 44 dev => 0, 38 - qid => e9p:make_qid(directory, 0, 0, []), 45 + qid => e9p:make_qid(regular, 0, 0), 39 46 mode => 0, 40 47 atime => 0, 41 48 mtime => 0, ··· 50 57 Out = e9p_msg:encode(Tag, Msg), 51 58 Decoded = e9p_msg:parse(iolist_to_binary(Out)), 52 59 ?assertEqual({ok, Tag, Msg}, Decoded). 60 + 61 + rclunk_encode_decode(_Conf) -> 62 + enc_dec(#rclunk{}). 63 + 64 + rflush_encode_decode(_Conf) -> 65 + enc_dec(#rflush{}). 66 + 67 + rwstat_encode_decode(_Conf) -> 68 + enc_dec(#rwstat{}). 69 + 70 + rremove_encode_decode(_Conf) -> 71 + enc_dec(#rremove{}). 72 + 73 + enc_dec(Data) -> 74 + Tag = 1, 75 + Out = e9p_msg:encode(Tag, Data), 76 + Encoded = iolist_to_binary(Out), 77 + ?assertEqual({ok, Tag, Data}, e9p_msg:parse(Encoded)).
+145
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 + Cmd = io_lib:format("9pfs -p ~B localhost ~s", 30 + [Port, Path]), 31 + ct:pal(Cmd), 32 + _Out = os:cmd(Cmd, #{ exception_on_failure => true }), 33 + [{sysfs, PID}, {mount, Path} | Config]. 34 + 35 + end_per_suite(Config) -> 36 + PID = ?config(sysfs, Config), 37 + Mount = ?config(mount, Config), 38 + os:cmd(["umount ", Mount], #{exception_on_failure => true}), 39 + erlang:exit(PID, normal), 40 + Config. 41 + 42 + can_list_mount_content(Config) -> 43 + Mount = ?config(mount, Config), 44 + ct:pal(Mount), 45 + ?assertEqual([ 46 + "applications", 47 + "processes", 48 + "system_info" 49 + ], ls(Mount)). 50 + 51 + current_process_is_listed(Config) -> 52 + Mount = ?config(mount, Config), 53 + Path = filename:join([Mount, "processes", pid_to_list(self())]), 54 + ?assertEqual([ 55 + "current_function", 56 + "dictionary", 57 + "error_handler", 58 + "garbage_collection", 59 + "group_leader", 60 + "heap_size", 61 + "initial_call", 62 + "links", 63 + "message_queue_len", 64 + "priority", 65 + "reductions", 66 + "stack_size", 67 + "status", 68 + "total_heap_size", 69 + "trap_exit" 70 + ], ls(Path)). 71 + 72 + current_process_current_function(Config) -> 73 + Mount = ?config(mount, Config), 74 + Path = filename:join([ 75 + Mount, 76 + "processes", 77 + pid_to_list(self()), 78 + "current_function" 79 + ]), 80 + {ok, Bin} = file:read_file(Path), 81 + ?assertEqual(~"{gen,do_call,4}", Bin). 82 + 83 + system_info_atom_count(Config) -> 84 + Mount = ?config(mount, Config), 85 + Path = filename:join([ 86 + Mount, 87 + "system_info", 88 + "atom_count" 89 + ]), 90 + {ok, Bin} = file:read_file(Path), 91 + ?assertEqual(erlang:system_info(atom_count), binary_to_integer(Bin)). 92 + 93 + applications_list(Config) -> 94 + Mount = ?config(mount, Config), 95 + Path = filename:join([ 96 + Mount, 97 + "applications" 98 + ]), 99 + List = ls(Path), 100 + Apps = lists:sort(lists:map(fun({AppName, _, _}) -> atom_to_list(AppName) end, 101 + application:loaded_applications())), 102 + ?assertEqual(Apps, List). 103 + 104 + application_info(Config) -> 105 + Mount = ?config(mount, Config), 106 + Path = filename:join([ 107 + Mount, 108 + "applications", 109 + "kernel" 110 + ]), 111 + List = ls(Path), 112 + ?assertEqual([ 113 + "applications", 114 + "description", 115 + "env", 116 + "id", 117 + "included_applications", 118 + "maxP", 119 + "maxT", 120 + "mod", 121 + "modules", 122 + "optional_applications", 123 + "registered", 124 + "start_phases", 125 + "vsn" 126 + ], List). 127 + 128 + application_env(Config) -> 129 + Mount = ?config(mount, Config), 130 + Path = filename:join([ 131 + Mount, 132 + "applications", 133 + "kernel", 134 + "env" 135 + ]), 136 + {ok, [Read]} = file:consult(Path), 137 + Env = application:get_all_env(kernel), 138 + ?assertEqual(Read, Env). 139 + 140 + %% Helpers 141 + 142 + ls(Path) -> 143 + {ok, Files} = file:list_dir(Path), 144 + % Remove `.fscache` added on macOS 145 + lists:sort(Files) -- [".fscache"].
+66
test/e9p_unfs_SUITE.erl
··· 1 + -module(e9p_unfs_SUITE). 2 + 3 + -compile(export_all). 4 + 5 + -include_lib("stdlib/include/assert.hrl"). 6 + -include_lib("common_test/include/ct.hrl"). 7 + 8 + all() -> [ 9 + list_mount, 10 + read_file 11 + %% Ignore this test for now, as UID/GID translation is not possible 12 + %% right now until we implement 9p2000.u extension 13 + % write_to_existing_file 14 + ]. 15 + 16 + init_per_suite(Config) -> 17 + PrivDir = ?config(priv_dir, Config), 18 + Source = filename:join(PrivDir, "h"), 19 + Mount = filename:join(PrivDir, "c"), 20 + Port = 6666, 21 + ok = file:make_dir(Source), 22 + ok = file:make_dir(Mount), 23 + {ok, PID} = e9p_server:start(Port, {e9p_unfs, #{path => Source}}), 24 + ct:pal("source: ~s", [Source]), 25 + ct:pal("mount: ~s", [Mount]), 26 + Cmd = io_lib:format("9pfs -p ~B localhost ~p", 27 + [Port, Mount]), 28 + ct:pal(Cmd), 29 + _Out = os:cmd(Cmd, #{ exception_on_failure => true }), 30 + [{fs, PID}, {mount, Mount}, {source, Source} | Config]. 31 + 32 + end_per_suite(Config) -> 33 + PID = ?config(fs, Config), 34 + Mount = ?config(mount, Config), 35 + os:cmd(["umount ", Mount], #{exception_on_failure => true}), 36 + erlang:exit(PID, normal), 37 + Config. 38 + 39 + list_mount(Config) -> 40 + Source = ?config(source, Config), 41 + Mount = ?config(mount, Config), 42 + file:write_file([Source, "/bar"], "example data"), 43 + ct:pal(Mount), 44 + ?assertEqual(["bar"], ls(Mount)). 45 + 46 + read_file(Config) -> 47 + Source = ?config(source, Config), 48 + Mount = ?config(mount, Config), 49 + file:write_file([Source, "/bar"], "example data"), 50 + ct:pal(Mount), 51 + ?assertEqual({ok, ~"example data"}, file:read_file([Mount, "/bar"])). 52 + 53 + write_to_existing_file(Config) -> 54 + Source = ?config(source, Config), 55 + Mount = ?config(mount, Config), 56 + file:write_file([Source, "/bar"], ""), 57 + ct:pal(Mount), 58 + ok = file:write_file([Mount, "/bar"], "another data"), 59 + ?assertEqual({ok, ~"another data"}, file:read_file([Source, "/bar"])). 60 + 61 + %% Helpers 62 + 63 + ls(Path) -> 64 + {ok, Files} = file:list_dir(Path), 65 + % Remove `.fscache` added on macOS 66 + lists:sort(Files) -- [".fscache"].
+97 -21
test/prop_e9p_msg.erl
··· 13 13 Max = 1 bsl (N * 8) - 1, 14 14 integer(Min, Max). 15 15 16 - afid() -> int(2). 16 + fid() -> int(2). 17 17 18 18 bin_str() -> ?LET({Charlist}, {string()}, 19 19 unicode:characters_to_binary(Charlist)). 20 20 21 - prop_can_decode_encoded_tversion() -> 22 - ?FORALL({Version, MPS}, {bin_str(), afid()}, 23 - begin 24 - enc_dec(#tversion{version = Version, max_packet_size = MPS}) 25 - end). 21 + qid_type() -> union([directory, 22 + append, 23 + excl, 24 + device, 25 + auth, 26 + tmp, 27 + symlink, 28 + regular 29 + ]). 26 30 27 - prop_can_decode_encoded_rversion() -> 28 - ?FORALL({Version, MPS}, {bin_str(), afid()}, 29 - begin 30 - enc_dec(#rversion{version = Version, max_packet_size = MPS}) 31 - end). 31 + qid() -> 32 + ?LET({Type, Version, Path}, {qid_type(), int(4), int(8)}, 33 + e9p:make_qid(Type, Version, Path)). 34 + 35 + prop_tversion() -> 36 + ?FORALL({Version, MPS}, {bin_str(), int(4)}, 37 + enc_dec(#tversion{version = Version, max_packet_size = MPS})). 38 + prop_rversion() -> 39 + ?FORALL({Version, MPS}, {bin_str(), fid()}, 40 + enc_dec(#rversion{version = Version, max_packet_size = MPS})). 32 41 33 - prop_can_decode_encoded_tauth() -> 34 - ?FORALL({Afid, Uname, Aname}, {afid(), bin_str(), bin_str()}, 35 - begin 36 - enc_dec(#tauth{afid = Afid, uname = Uname, aname = Aname}) 37 - end). 42 + prop_tauth() -> 43 + ?FORALL({Afid, Uname, Aname}, {fid(), bin_str(), bin_str()}, 44 + enc_dec(#tauth{afid = Afid, 45 + uname = Uname, 46 + aname = Aname})). 47 + prop_rauth() -> 48 + ?FORALL({AQID}, {qid()}, 49 + enc_dec(#rauth{aqid = AQID})). 38 50 39 - prop_can_decode_encoded_rerror() -> 51 + prop_rerror() -> 40 52 ?FORALL({Msg}, {bin_str()}, 41 53 begin 42 54 enc_dec(#rerror{msg = Msg}) 43 55 end). 44 56 45 - prop_can_decode_encoded_rstat() -> 46 - ?FORALL({Type, Dev, Mode, Atime, Mtime, Len, Name, Uid, Gid, Muid}, 47 - {int(2), int(2), int(4), int(4), int(4), int(8), bin_str(), bin_str(), 57 + prop_tattach() -> 58 + ?FORALL({FID, AFID, Uname, Aname}, {fid(), fid(), bin_str(), bin_str()}, 59 + enc_dec(#tattach{ 60 + fid = FID, 61 + afid = AFID, 62 + uname = Uname, 63 + aname = Aname 64 + })). 65 + prop_rattach() -> 66 + ?FORALL({QID}, {qid()}, 67 + enc_dec(#rattach{qid = QID})). 68 + 69 + prop_twalk() -> 70 + ?FORALL({FID, NewFID, Names}, {fid(), fid(), list(bin_str())}, 71 + enc_dec(#twalk{fid = FID, new_fid = NewFID, names = Names})). 72 + prop_rwalk() -> 73 + ?FORALL({QIDs}, {list(qid())}, 74 + enc_dec(#rwalk{qids = QIDs})). 75 + 76 + prop_topen() -> 77 + ?FORALL({FID, Mode}, {fid(), int(1)}, 78 + enc_dec(#topen{fid = FID, mode = Mode})). 79 + prop_ropen() -> 80 + ?FORALL({QID, IOUnit}, {qid(), int(4)}, 81 + enc_dec(#ropen{qid = QID, io_unit = IOUnit})). 82 + 83 + prop_tcreate() -> 84 + ?FORALL({FID, Name, Perm, Mode}, {fid(), bin_str(), int(4), int(1)}, 85 + enc_dec(#tcreate{fid = FID, 86 + name = Name, 87 + perm = Perm, 88 + mode = Mode})). 89 + prop_rcreate() -> 90 + ?FORALL({QID, IOUnit}, {qid(), int(4)}, 91 + enc_dec(#rcreate{qid = QID, io_unit = IOUnit})). 92 + 93 + prop_tremove() -> 94 + ?FORALL({FID}, {fid()}, 95 + enc_dec(#tremove{fid = FID})). 96 + 97 + prop_tclunk() -> 98 + ?FORALL({FID}, {fid()}, 99 + enc_dec(#tclunk{fid = FID})). 100 + 101 + prop_tread() -> 102 + ?FORALL({FID, Offset, Len}, {fid(), int(8), int(4)}, 103 + enc_dec(#tread{fid = FID, offset = Offset, len = Len})). 104 + prop_rread() -> 105 + ?FORALL({Data}, {binary()}, 106 + enc_dec(#rread{data = Data})). 107 + 108 + prop_twrite() -> 109 + ?FORALL({FID, Offset, Data}, {fid(), int(8), binary()}, 110 + enc_dec(#twrite{fid = FID, offset = Offset, data = Data})). 111 + prop_rwrite() -> 112 + ?FORALL({Len}, {int(4)}, 113 + enc_dec(#rwrite{len = Len})). 114 + 115 + prop_tstat() -> 116 + ?FORALL({FID}, {fid()}, 117 + enc_dec(#tstat{fid = FID})). 118 + prop_rstat() -> 119 + ?FORALL({QID, Type, Dev, Mode, Atime, Mtime, Len, Name, Uid, Gid, Muid}, 120 + {qid(), int(2), int(2), integer(0, 8#777), int(4), int(4), int(8), bin_str(), bin_str(), 48 121 bin_str(), bin_str()}, 49 122 begin 50 123 enc_dec(#rstat{ 51 124 stat = #{ 52 125 type => Type, 53 126 dev => Dev, 54 - qid => e9p:make_qid(directory, 0, 0, []), 127 + qid => QID, 55 128 mode => Mode, 56 129 atime => Atime, 57 130 mtime => Mtime, ··· 62 135 muid => Muid 63 136 }}) 64 137 end). 138 + 139 + prop_tflush() -> 140 + ?FORALL({Tag}, {int(2)}, enc_dec(#tflush{tag = Tag})). 65 141 66 142 enc_dec(Data) -> 67 143 Tag = 1,