tangled
alpha
login
or
join now
hauleth.dev
/
e9p
Pure Erlang implementation of 9p2000 protocol
filesystem
fs
9p2000
erlang
9p
7
fork
atom
overview
issues
4
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
master
v0.1.0
compare:
master
v0.1.0
go
+410
-89
13 changed files
expand all
collapse all
unified
split
flake.nix
nix
kamid
mode_format.patch.license
rebar.config
src
e9p_msg.erl
e9p_server.erl
e9p_sysfs.erl
e9p_transport.erl
e9p_unfs.erl
e9p_utils.erl
test
e9p_msg_SUITE.erl
e9p_sysfs_SUITE.erl
e9p_unfs_SUITE.erl
prop_e9p_msg.erl
+8
-8
flake.nix
···
34
34
devenv.shells.default = {
35
35
languages.erlang.enable = true;
36
36
37
37
-
packages = [ kamid ];
37
37
+
packages = [ kamid pkgs._9pfs ];
38
38
39
39
-
env.ERL_FLAGS = ''
40
40
-
-kernel logger_level debug
41
41
-
-kernel logger '[
42
42
-
{filters, log, [{no_progress, {fun logger_filters:progress/2, stop}}]}
43
43
-
]'
44
44
-
-kernel shell_history enabled
45
45
-
'';
39
39
+
# env.ERL_FLAGS = ''
40
40
+
# -kernel logger_level debug
41
41
+
# -kernel logger '[
42
42
+
# {filters, log, [{no_progress, {fun logger_filters:progress/2, stop}}]}
43
43
+
# ]'
44
44
+
# -kernel shell_history enabled
45
45
+
# '';
46
46
};
47
47
};
48
48
};
+3
nix/kamid/mode_format.patch.license
···
1
1
+
SPDX-FileCopyrightText: 2026 ลukasz Niemier <~@hauleth.dev>
2
2
+
3
3
+
SPDX-License-Identifier: Apache-2.0
+4
-1
rebar.config
···
9
9
10
10
{project_plugins, [
11
11
rebar3_ex_doc,
12
12
-
rebar3_proper
12
12
+
rebar3_proper,
13
13
+
covertool
13
14
]}.
14
15
15
16
{profiles, [{test, [
16
17
{deps, [proper]},
17
18
{erl_opts, [nowarn_export_all]}
18
19
]}]}.
20
20
+
21
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
163
-
do_parse(?Rread, <<Count:4/?int, Data:Count/?int>>) ->
163
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
176
-
QID:13/binary,
176
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
186
+
QID = binary_to_qid(RawQID),
187
187
+
Flags = qid_to_mode_flags(QID),
186
188
{ok, #{
187
189
type => Type,
188
190
dev => Dev,
189
189
-
qid => binary_to_qid(QID),
190
190
-
mode => Mode,
191
191
+
qid => QID,
192
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
311
-
FullMode = case e9p:is_type(QID, directory) of
312
312
-
true -> 16#80000000 bor Mode;
313
313
-
false -> Mode
314
314
-
end,
313
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
331
+
qid_to_mode_flags(#qid{type = Type}) ->
332
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
11
-
-export([start_link/2,
12
12
-
setup_acceptor/3,
11
11
+
-export([start/2,
12
12
+
start_link/2]).
13
13
+
14
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
26
+
27
27
+
start(Port, Handler) ->
28
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
1
+
% SPDX-FileCopyrightText: 2026 ลukasz Niemier <~@hauleth.dev>
2
2
+
%
3
3
+
% SPDX-License-Identifier: Apache-2.0
4
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
42
-
~"logocal_processors",
46
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
51
-
~"max_heap_type",
55
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
66
-
~"os_time_source",
70
70
+
~"os_system_time_source",
67
71
~"start_time",
68
68
-
~"time_correlation",
72
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
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
128
-
Val = erlang:system_info(Key),
129
129
-
Data = iolist_to_binary(io_lib:format("~p", [Val])),
131
131
+
Data = case erlang:system_info(Key) of
132
132
+
Val when is_binary(Val) -> Val;
133
133
+
Val ->
134
134
+
case io_lib:printable_list(Val) of
135
135
+
true -> unicode:characters_to_binary(Val);
136
136
+
false -> iolist_to_binary(io_lib:format("~p", [Val]))
137
137
+
end
138
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
180
-
Data = iolist_to_binary(io_lib:format("~p", [AllEnv])),
189
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
19
-
case gen_tcp:recv(Socket, 4) of
20
20
-
{ok, <<Size:4/?int>>} ->
21
21
-
case gen_tcp:recv(Socket, Size - 4) of
22
22
-
{ok, Data} when is_binary(Data) ->
23
23
-
case e9p_msg:parse(Data) of
24
24
-
{ok, Tag, Msg} ->
25
25
-
{ok, Tag, Msg};
26
26
-
{error, _} = Error ->
27
27
-
Error
28
28
-
end;
29
29
-
{error, _} = Error ->
30
30
-
Error
31
31
-
end;
32
32
-
{error, _} = Error ->
33
33
-
Error
19
19
+
maybe
20
20
+
{ok, <<Size:4/?int>>} ?= gen_tcp:recv(Socket, 4),
21
21
+
{ok, Data} ?= gen_tcp:recv(Socket, Size - 4),
22
22
+
true = is_binary(Data),
23
23
+
{ok, Tag, Msg} ?= e9p_msg:parse(Data),
24
24
+
{ok, Tag, Msg}
25
25
+
else
26
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
22
-
case file:read_file_info(FullPath, [{time, posix}]) of
22
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
93
-
case file:read_file_info(FullPath, [{time, posix}]) of
93
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
106
-
case file:write_file_info(FullPath, FileInfo, [{time, posix}]) of
106
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
118
-
{ok, List} = file:list_dir(FullPath),
118
118
+
% Currently `file` module do not expose raw mode for listing
119
119
+
% file directory, so we need to call private `prim_file` module
120
120
+
% to access such functionality. Otherwise we can encounter
121
121
+
% deadlock.
122
122
+
%
123
123
+
% See: https://github.com/erlang/otp/issues/10593
124
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
144
-
true -> file:del_dir(FullPath);
145
145
-
false -> file:delete(FullPath)
150
150
+
% Currently `file` module do not expose raw mode for listing
151
151
+
% file directory, so we need to call private `prim_file` module
152
152
+
% to access such functionality. Otherwise we can encounter
153
153
+
% deadlock.
154
154
+
%
155
155
+
% See: https://github.com/erlang/otp/issues/10593
156
156
+
true -> prim_file:del_dir(FullPath);
157
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
1
-
% SPDX-FileCopyrightText: 2025 ลukasz Niemier <~@hauleth.dev>
2
2
-
%
3
3
-
% SPDX-License-Identifier: Apache-2.0
4
4
-
5
5
-
-module(e9p_utils).
6
6
-
7
7
-
-export([normalize_path/1]).
8
8
-
9
9
-
normalize_path(List) -> normalize_path(List, []).
10
10
-
11
11
-
normalize_path([], Acc) -> lists:reverse(Acc);
12
12
-
normalize_path([Dot | Rest], Acc)
13
13
-
when Dot =:= "." orelse Dot =:= ~"."
14
14
-
->
15
15
-
normalize_path(Rest, Acc);
16
16
-
normalize_path([P | Rest], Acc) ->
17
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
14
-
all() -> [stat_encode_decode, rstat_encode_decode].
14
14
+
all() -> [
15
15
+
stat_encode_decode,
16
16
+
rstat_encode_decode,
17
17
+
rclunk_encode_decode,
18
18
+
rflush_encode_decode,
19
19
+
rwstat_encode_decode,
20
20
+
rremove_encode_decode
21
21
+
].
15
22
16
23
stat_encode_decode(_Conf) ->
17
24
Stat = #{
18
25
type => 0,
19
26
dev => 0,
20
20
-
qid => e9p:make_qid(directory, 0, 0, []),
27
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
38
-
qid => e9p:make_qid(directory, 0, 0, []),
45
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
60
+
61
61
+
rclunk_encode_decode(_Conf) ->
62
62
+
enc_dec(#rclunk{}).
63
63
+
64
64
+
rflush_encode_decode(_Conf) ->
65
65
+
enc_dec(#rflush{}).
66
66
+
67
67
+
rwstat_encode_decode(_Conf) ->
68
68
+
enc_dec(#rwstat{}).
69
69
+
70
70
+
rremove_encode_decode(_Conf) ->
71
71
+
enc_dec(#rremove{}).
72
72
+
73
73
+
enc_dec(Data) ->
74
74
+
Tag = 1,
75
75
+
Out = e9p_msg:encode(Tag, Data),
76
76
+
Encoded = iolist_to_binary(Out),
77
77
+
?assertEqual({ok, Tag, Data}, e9p_msg:parse(Encoded)).
+145
test/e9p_sysfs_SUITE.erl
···
1
1
+
% SPDX-FileCopyrightText: 2026 ลukasz Niemier <~@hauleth.dev>
2
2
+
%
3
3
+
% SPDX-License-Identifier: Apache-2.0
4
4
+
5
5
+
-module(e9p_sysfs_SUITE).
6
6
+
7
7
+
-compile(export_all).
8
8
+
9
9
+
-include_lib("stdlib/include/assert.hrl").
10
10
+
-include_lib("common_test/include/ct.hrl").
11
11
+
12
12
+
all() -> [
13
13
+
can_list_mount_content,
14
14
+
current_process_is_listed,
15
15
+
current_process_current_function,
16
16
+
system_info_atom_count,
17
17
+
applications_list,
18
18
+
application_info,
19
19
+
application_env
20
20
+
].
21
21
+
22
22
+
init_per_suite(Config) ->
23
23
+
PrivDir = ?config(priv_dir, Config),
24
24
+
Path = filename:join(PrivDir, "sysfs"),
25
25
+
Port = 19999,
26
26
+
ok = file:make_dir(Path),
27
27
+
{ok, PID} = e9p_server:start(Port, {e9p_sysfs, []}),
28
28
+
ct:pal(Path),
29
29
+
Cmd = io_lib:format("9pfs -p ~B localhost ~s",
30
30
+
[Port, Path]),
31
31
+
ct:pal(Cmd),
32
32
+
_Out = os:cmd(Cmd, #{ exception_on_failure => true }),
33
33
+
[{sysfs, PID}, {mount, Path} | Config].
34
34
+
35
35
+
end_per_suite(Config) ->
36
36
+
PID = ?config(sysfs, Config),
37
37
+
Mount = ?config(mount, Config),
38
38
+
os:cmd(["umount ", Mount], #{exception_on_failure => true}),
39
39
+
erlang:exit(PID, normal),
40
40
+
Config.
41
41
+
42
42
+
can_list_mount_content(Config) ->
43
43
+
Mount = ?config(mount, Config),
44
44
+
ct:pal(Mount),
45
45
+
?assertEqual([
46
46
+
"applications",
47
47
+
"processes",
48
48
+
"system_info"
49
49
+
], ls(Mount)).
50
50
+
51
51
+
current_process_is_listed(Config) ->
52
52
+
Mount = ?config(mount, Config),
53
53
+
Path = filename:join([Mount, "processes", pid_to_list(self())]),
54
54
+
?assertEqual([
55
55
+
"current_function",
56
56
+
"dictionary",
57
57
+
"error_handler",
58
58
+
"garbage_collection",
59
59
+
"group_leader",
60
60
+
"heap_size",
61
61
+
"initial_call",
62
62
+
"links",
63
63
+
"message_queue_len",
64
64
+
"priority",
65
65
+
"reductions",
66
66
+
"stack_size",
67
67
+
"status",
68
68
+
"total_heap_size",
69
69
+
"trap_exit"
70
70
+
], ls(Path)).
71
71
+
72
72
+
current_process_current_function(Config) ->
73
73
+
Mount = ?config(mount, Config),
74
74
+
Path = filename:join([
75
75
+
Mount,
76
76
+
"processes",
77
77
+
pid_to_list(self()),
78
78
+
"current_function"
79
79
+
]),
80
80
+
{ok, Bin} = file:read_file(Path),
81
81
+
?assertEqual(~"{gen,do_call,4}", Bin).
82
82
+
83
83
+
system_info_atom_count(Config) ->
84
84
+
Mount = ?config(mount, Config),
85
85
+
Path = filename:join([
86
86
+
Mount,
87
87
+
"system_info",
88
88
+
"atom_count"
89
89
+
]),
90
90
+
{ok, Bin} = file:read_file(Path),
91
91
+
?assertEqual(erlang:system_info(atom_count), binary_to_integer(Bin)).
92
92
+
93
93
+
applications_list(Config) ->
94
94
+
Mount = ?config(mount, Config),
95
95
+
Path = filename:join([
96
96
+
Mount,
97
97
+
"applications"
98
98
+
]),
99
99
+
List = ls(Path),
100
100
+
Apps = lists:sort(lists:map(fun({AppName, _, _}) -> atom_to_list(AppName) end,
101
101
+
application:loaded_applications())),
102
102
+
?assertEqual(Apps, List).
103
103
+
104
104
+
application_info(Config) ->
105
105
+
Mount = ?config(mount, Config),
106
106
+
Path = filename:join([
107
107
+
Mount,
108
108
+
"applications",
109
109
+
"kernel"
110
110
+
]),
111
111
+
List = ls(Path),
112
112
+
?assertEqual([
113
113
+
"applications",
114
114
+
"description",
115
115
+
"env",
116
116
+
"id",
117
117
+
"included_applications",
118
118
+
"maxP",
119
119
+
"maxT",
120
120
+
"mod",
121
121
+
"modules",
122
122
+
"optional_applications",
123
123
+
"registered",
124
124
+
"start_phases",
125
125
+
"vsn"
126
126
+
], List).
127
127
+
128
128
+
application_env(Config) ->
129
129
+
Mount = ?config(mount, Config),
130
130
+
Path = filename:join([
131
131
+
Mount,
132
132
+
"applications",
133
133
+
"kernel",
134
134
+
"env"
135
135
+
]),
136
136
+
{ok, [Read]} = file:consult(Path),
137
137
+
Env = application:get_all_env(kernel),
138
138
+
?assertEqual(Read, Env).
139
139
+
140
140
+
%% Helpers
141
141
+
142
142
+
ls(Path) ->
143
143
+
{ok, Files} = file:list_dir(Path),
144
144
+
% Remove `.fscache` added on macOS
145
145
+
lists:sort(Files) -- [".fscache"].
+66
test/e9p_unfs_SUITE.erl
···
1
1
+
-module(e9p_unfs_SUITE).
2
2
+
3
3
+
-compile(export_all).
4
4
+
5
5
+
-include_lib("stdlib/include/assert.hrl").
6
6
+
-include_lib("common_test/include/ct.hrl").
7
7
+
8
8
+
all() -> [
9
9
+
list_mount,
10
10
+
read_file
11
11
+
%% Ignore this test for now, as UID/GID translation is not possible
12
12
+
%% right now until we implement 9p2000.u extension
13
13
+
% write_to_existing_file
14
14
+
].
15
15
+
16
16
+
init_per_suite(Config) ->
17
17
+
PrivDir = ?config(priv_dir, Config),
18
18
+
Source = filename:join(PrivDir, "h"),
19
19
+
Mount = filename:join(PrivDir, "c"),
20
20
+
Port = 6666,
21
21
+
ok = file:make_dir(Source),
22
22
+
ok = file:make_dir(Mount),
23
23
+
{ok, PID} = e9p_server:start(Port, {e9p_unfs, #{path => Source}}),
24
24
+
ct:pal("source: ~s", [Source]),
25
25
+
ct:pal("mount: ~s", [Mount]),
26
26
+
Cmd = io_lib:format("9pfs -p ~B localhost ~p",
27
27
+
[Port, Mount]),
28
28
+
ct:pal(Cmd),
29
29
+
_Out = os:cmd(Cmd, #{ exception_on_failure => true }),
30
30
+
[{fs, PID}, {mount, Mount}, {source, Source} | Config].
31
31
+
32
32
+
end_per_suite(Config) ->
33
33
+
PID = ?config(fs, Config),
34
34
+
Mount = ?config(mount, Config),
35
35
+
os:cmd(["umount ", Mount], #{exception_on_failure => true}),
36
36
+
erlang:exit(PID, normal),
37
37
+
Config.
38
38
+
39
39
+
list_mount(Config) ->
40
40
+
Source = ?config(source, Config),
41
41
+
Mount = ?config(mount, Config),
42
42
+
file:write_file([Source, "/bar"], "example data"),
43
43
+
ct:pal(Mount),
44
44
+
?assertEqual(["bar"], ls(Mount)).
45
45
+
46
46
+
read_file(Config) ->
47
47
+
Source = ?config(source, Config),
48
48
+
Mount = ?config(mount, Config),
49
49
+
file:write_file([Source, "/bar"], "example data"),
50
50
+
ct:pal(Mount),
51
51
+
?assertEqual({ok, ~"example data"}, file:read_file([Mount, "/bar"])).
52
52
+
53
53
+
write_to_existing_file(Config) ->
54
54
+
Source = ?config(source, Config),
55
55
+
Mount = ?config(mount, Config),
56
56
+
file:write_file([Source, "/bar"], ""),
57
57
+
ct:pal(Mount),
58
58
+
ok = file:write_file([Mount, "/bar"], "another data"),
59
59
+
?assertEqual({ok, ~"another data"}, file:read_file([Source, "/bar"])).
60
60
+
61
61
+
%% Helpers
62
62
+
63
63
+
ls(Path) ->
64
64
+
{ok, Files} = file:list_dir(Path),
65
65
+
% Remove `.fscache` added on macOS
66
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
16
-
afid() -> int(2).
16
16
+
fid() -> int(2).
17
17
18
18
bin_str() -> ?LET({Charlist}, {string()},
19
19
unicode:characters_to_binary(Charlist)).
20
20
21
21
-
prop_can_decode_encoded_tversion() ->
22
22
-
?FORALL({Version, MPS}, {bin_str(), afid()},
23
23
-
begin
24
24
-
enc_dec(#tversion{version = Version, max_packet_size = MPS})
25
25
-
end).
21
21
+
qid_type() -> union([directory,
22
22
+
append,
23
23
+
excl,
24
24
+
device,
25
25
+
auth,
26
26
+
tmp,
27
27
+
symlink,
28
28
+
regular
29
29
+
]).
26
30
27
27
-
prop_can_decode_encoded_rversion() ->
28
28
-
?FORALL({Version, MPS}, {bin_str(), afid()},
29
29
-
begin
30
30
-
enc_dec(#rversion{version = Version, max_packet_size = MPS})
31
31
-
end).
31
31
+
qid() ->
32
32
+
?LET({Type, Version, Path}, {qid_type(), int(4), int(8)},
33
33
+
e9p:make_qid(Type, Version, Path)).
34
34
+
35
35
+
prop_tversion() ->
36
36
+
?FORALL({Version, MPS}, {bin_str(), int(4)},
37
37
+
enc_dec(#tversion{version = Version, max_packet_size = MPS})).
38
38
+
prop_rversion() ->
39
39
+
?FORALL({Version, MPS}, {bin_str(), fid()},
40
40
+
enc_dec(#rversion{version = Version, max_packet_size = MPS})).
32
41
33
33
-
prop_can_decode_encoded_tauth() ->
34
34
-
?FORALL({Afid, Uname, Aname}, {afid(), bin_str(), bin_str()},
35
35
-
begin
36
36
-
enc_dec(#tauth{afid = Afid, uname = Uname, aname = Aname})
37
37
-
end).
42
42
+
prop_tauth() ->
43
43
+
?FORALL({Afid, Uname, Aname}, {fid(), bin_str(), bin_str()},
44
44
+
enc_dec(#tauth{afid = Afid,
45
45
+
uname = Uname,
46
46
+
aname = Aname})).
47
47
+
prop_rauth() ->
48
48
+
?FORALL({AQID}, {qid()},
49
49
+
enc_dec(#rauth{aqid = AQID})).
38
50
39
39
-
prop_can_decode_encoded_rerror() ->
51
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
45
-
prop_can_decode_encoded_rstat() ->
46
46
-
?FORALL({Type, Dev, Mode, Atime, Mtime, Len, Name, Uid, Gid, Muid},
47
47
-
{int(2), int(2), int(4), int(4), int(4), int(8), bin_str(), bin_str(),
57
57
+
prop_tattach() ->
58
58
+
?FORALL({FID, AFID, Uname, Aname}, {fid(), fid(), bin_str(), bin_str()},
59
59
+
enc_dec(#tattach{
60
60
+
fid = FID,
61
61
+
afid = AFID,
62
62
+
uname = Uname,
63
63
+
aname = Aname
64
64
+
})).
65
65
+
prop_rattach() ->
66
66
+
?FORALL({QID}, {qid()},
67
67
+
enc_dec(#rattach{qid = QID})).
68
68
+
69
69
+
prop_twalk() ->
70
70
+
?FORALL({FID, NewFID, Names}, {fid(), fid(), list(bin_str())},
71
71
+
enc_dec(#twalk{fid = FID, new_fid = NewFID, names = Names})).
72
72
+
prop_rwalk() ->
73
73
+
?FORALL({QIDs}, {list(qid())},
74
74
+
enc_dec(#rwalk{qids = QIDs})).
75
75
+
76
76
+
prop_topen() ->
77
77
+
?FORALL({FID, Mode}, {fid(), int(1)},
78
78
+
enc_dec(#topen{fid = FID, mode = Mode})).
79
79
+
prop_ropen() ->
80
80
+
?FORALL({QID, IOUnit}, {qid(), int(4)},
81
81
+
enc_dec(#ropen{qid = QID, io_unit = IOUnit})).
82
82
+
83
83
+
prop_tcreate() ->
84
84
+
?FORALL({FID, Name, Perm, Mode}, {fid(), bin_str(), int(4), int(1)},
85
85
+
enc_dec(#tcreate{fid = FID,
86
86
+
name = Name,
87
87
+
perm = Perm,
88
88
+
mode = Mode})).
89
89
+
prop_rcreate() ->
90
90
+
?FORALL({QID, IOUnit}, {qid(), int(4)},
91
91
+
enc_dec(#rcreate{qid = QID, io_unit = IOUnit})).
92
92
+
93
93
+
prop_tremove() ->
94
94
+
?FORALL({FID}, {fid()},
95
95
+
enc_dec(#tremove{fid = FID})).
96
96
+
97
97
+
prop_tclunk() ->
98
98
+
?FORALL({FID}, {fid()},
99
99
+
enc_dec(#tclunk{fid = FID})).
100
100
+
101
101
+
prop_tread() ->
102
102
+
?FORALL({FID, Offset, Len}, {fid(), int(8), int(4)},
103
103
+
enc_dec(#tread{fid = FID, offset = Offset, len = Len})).
104
104
+
prop_rread() ->
105
105
+
?FORALL({Data}, {binary()},
106
106
+
enc_dec(#rread{data = Data})).
107
107
+
108
108
+
prop_twrite() ->
109
109
+
?FORALL({FID, Offset, Data}, {fid(), int(8), binary()},
110
110
+
enc_dec(#twrite{fid = FID, offset = Offset, data = Data})).
111
111
+
prop_rwrite() ->
112
112
+
?FORALL({Len}, {int(4)},
113
113
+
enc_dec(#rwrite{len = Len})).
114
114
+
115
115
+
prop_tstat() ->
116
116
+
?FORALL({FID}, {fid()},
117
117
+
enc_dec(#tstat{fid = FID})).
118
118
+
prop_rstat() ->
119
119
+
?FORALL({QID, Type, Dev, Mode, Atime, Mtime, Len, Name, Uid, Gid, Muid},
120
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
54
-
qid => e9p:make_qid(directory, 0, 0, []),
127
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
138
+
139
139
+
prop_tflush() ->
140
140
+
?FORALL({Tag}, {int(2)}, enc_dec(#tflush{tag = Tag})).
65
141
66
142
enc_dec(Data) ->
67
143
Tag = 1,