tangled
alpha
login
or
join now
hauleth.dev
/
e9p
9
fork
atom
Pure Erlang implementation of 9p2000 protocol
filesystem
fs
9p2000
erlang
9p
9
fork
atom
overview
issues
4
pulls
pipelines
Migrate from maps to record for internal messages encoding
hauleth.dev
1 month ago
6b29b56e
e0dea516
verified
This commit was signed with the committer's
known signature
.
hauleth.dev
SSH Key Fingerprint:
SHA256:1hEP8QO8nM2KQfQ8jK4Q19y/CmqVZQI/cNSht3c1QlI=
+335
-177
10 changed files
expand all
collapse all
unified
split
src
e9p.erl
e9p_client.erl
e9p_fs.erl
e9p_internal.hrl
e9p_msg.erl
e9p_server.erl
e9p_transport.erl
e9p_unfs.erl
e9p_utils.erl
test
prop_e9p_msg.erl
+1
-1
src/e9p.erl
···
27
27
%-spec make_qid()
28
28
make_qid(Type, Version, Path, State) ->
29
29
#{
30
30
-
type => e9p_utils:qtype_from_atom(Type),
30
30
+
type => e9p_utils:to_qtype(Type),
31
31
version => Version,
32
32
path => Path,
33
33
state => State
+12
-11
src/e9p_client.erl
···
22
22
init({Host, Port, _Opts}) ->
23
23
{ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, binary]),
24
24
case version_negotiation(Socket) of
25
25
-
{ok, #{max_packet_size := MaxPacketSize, version := ?version}} ->
25
25
+
{ok, #rversion{max_packet_size = MaxPacketSize, version = ?version}} ->
26
26
inet:setopts(Socket, [{active, once}]),
27
27
{ok,
28
28
#{socket => Socket,
···
32
32
msgs => #{},
33
33
max_packet_size => MaxPacketSize,
34
34
version => ?version}};
35
35
-
{ok, _, #{version := OtherVersion}} ->
35
35
+
{ok, #rversion{version = OtherVersion}} ->
36
36
{error, {unsupported_version, OtherVersion}};
37
37
{error, _} = Error ->
38
38
Error
···
51
51
Id ->
52
52
Id
53
53
end,
54
54
-
Msg = #{fid => Fid,
55
55
-
afid => Afid,
56
56
-
uname => Uname,
57
57
-
aname => Aname},
58
58
-
e9p_transport:send(Socket, Tag, tattach, Msg),
54
54
+
Msg = #tattach{fid = Fid,
55
55
+
afid = Afid,
56
56
+
uname = Uname,
57
57
+
aname = Aname},
58
58
+
e9p_transport:send(Socket, Tag, Msg),
59
59
{noreply,
60
60
State#{tag := Tag + 1,
61
61
fid := Fid + 1,
···
68
68
69
69
handle_info({tcp, Socket, Data}, #{socket := Socket} = State) ->
70
70
#{buffer := Buffer, msgs := Msgs0} = State,
71
71
+
inet:setopts(Socket, [{active, once}]),
71
72
case e9p_transport:read_stream(<<Buffer/binary, Data/binary>>) of
72
72
-
{ok, Tag, _Type, Msg, Rest} ->
73
73
+
{ok, Tag, Msg, Rest} ->
73
74
Msgs =
74
75
case maps:take(Tag, Msgs0) of
75
76
{{From, _}, M} ->
···
85
86
end.
86
87
87
88
version_negotiation(Socket) ->
88
88
-
Msg = #{max_packet_size => ?max_packet_size, version => ?version},
89
89
-
e9p_transport:send(Socket, notag, tversion, Msg),
89
89
+
Msg = #tversion{max_packet_size = ?max_packet_size, version = ?version},
90
90
+
e9p_transport:send(Socket, notag, Msg),
90
91
case e9p_transport:read(Socket) of
91
91
-
{ok, _, rversion, Resp} ->
92
92
+
{ok, _, Resp} ->
92
93
{ok, Resp};
93
94
{error, _} = Error ->
94
95
Error
+1
-1
src/e9p_fs.erl
···
56
56
-callback walk(QID :: e9p:qid(), unicode:chardata(), state()) ->
57
57
{e9p:qid() | false, state()}.
58
58
59
59
-
-callback open(QID :: e9p:qid(), state()) -> result(e9p:u32()).
59
59
+
-callback open(QID :: e9p:qid(), state()) -> result({e9p:qid(), e9p:u32()}).
60
60
61
61
-callback create(QID :: e9p:qid(),
62
62
Name :: unicode:chardata(),
+41
src/e9p_internal.hrl
···
52
52
53
53
-define(Twstat, 126).
54
54
-define(Rwstat, 127).
55
55
+
56
56
+
-record(tversion, {max_packet_size, version}).
57
57
+
-record(rversion, {max_packet_size, version}).
58
58
+
59
59
+
-record(tauth, {afid, uname, aname}).
60
60
+
-record(rauth, {aqid}).
61
61
+
62
62
+
-record(rerror, {msg}).
63
63
+
64
64
+
-record(tflush, {tag}).
65
65
+
-record(rflush, {}).
66
66
+
67
67
+
-record(tattach, {fid, afid, uname, aname}).
68
68
+
-record(rattach, {qid}).
69
69
+
70
70
+
-record(twalk, {fid, new_fid, names}).
71
71
+
-record(rwalk, {qids}).
72
72
+
73
73
+
-record(topen, {fid, mode}).
74
74
+
-record(ropen, {qid, io_unit}).
75
75
+
76
76
+
-record(tcreate, {fid, name, perm, mode}).
77
77
+
-record(rcreate, {qid, io_unit}).
78
78
+
79
79
+
-record(tread, {fid, offset, len}).
80
80
+
-record(rread, {data}).
81
81
+
82
82
+
-record(twrite, {fid, offset, data}).
83
83
+
-record(rwrite, {len}).
84
84
+
85
85
+
-record(tclunk, {fid}).
86
86
+
-record(rclunk, {}).
87
87
+
88
88
+
-record(tremove, {fid}).
89
89
+
-record(rremove, {}).
90
90
+
91
91
+
-record(tstat, {fid}).
92
92
+
-record(rstat, {stat}).
93
93
+
94
94
+
-record(twstat, {fid, stat}).
95
95
+
-record(rwstat, {}).
+96
-95
src/e9p_msg.erl
···
6
6
%% @end
7
7
-module(e9p_msg).
8
8
9
9
-
-export([parse/1, encode/3]).
9
9
+
-export([parse/1, encode/2, encode_stat/1]).
10
10
11
11
-export_type([tag/0,
12
12
-
message_type/0,
13
13
-
request_message_type/0,
14
14
-
response_message_type/0
12
12
+
message/0,
13
13
+
request_message/0,
14
14
+
response_message/0
15
15
]).
16
16
17
17
-include("e9p_internal.hrl").
18
18
19
19
-type tag() :: 16#0000..16#FFFF.
20
20
21
21
-
-type request_message_type() ::
22
22
-
tversion |
23
23
-
tauth |
24
24
-
tattach |
25
25
-
tflush |
26
26
-
twalk |
27
27
-
topen |
28
28
-
tcreate |
29
29
-
tread |
30
30
-
twrite |
31
31
-
tclunk |
32
32
-
tremove |
33
33
-
tstat |
34
34
-
twstat.
21
21
+
-type request_message() ::
22
22
+
#tversion{} |
23
23
+
#tauth{} |
24
24
+
#tattach{} |
25
25
+
#tflush{} |
26
26
+
#twalk{} |
27
27
+
#topen{} |
28
28
+
#tcreate{} |
29
29
+
#tread{} |
30
30
+
#twrite{} |
31
31
+
#tclunk{} |
32
32
+
#tremove{} |
33
33
+
#tstat{} |
34
34
+
#twstat{}.
35
35
36
36
-
-type response_message_type() ::
37
37
-
rversion |
38
38
-
rauth |
39
39
-
rattach |
40
40
-
rerror |
41
41
-
rflush |
42
42
-
rwalk |
43
43
-
ropen |
44
44
-
rcreate |
45
45
-
rread |
46
46
-
rwrite |
47
47
-
rclunk |
48
48
-
rstat |
49
49
-
rwstat.
36
36
+
-type response_message() ::
37
37
+
#rversion{} |
38
38
+
#rauth{} |
39
39
+
#rattach{} |
40
40
+
#rerror{} |
41
41
+
#rflush{} |
42
42
+
#rwalk{} |
43
43
+
#ropen{} |
44
44
+
#rcreate{} |
45
45
+
#rread{} |
46
46
+
#rwrite{} |
47
47
+
#rclunk{} |
48
48
+
#rstat{} |
49
49
+
#rwstat{}.
50
50
51
51
-
-type message_type() :: request_message_type() | response_message_type().
51
51
+
-type message() :: request_message() | response_message().
52
52
53
53
+
-spec parse(binary()) -> {ok, tag(), message()} | {error, term()}.
53
54
parse(<<Type:1/?int, Tag:2/?int, Data/binary>>) ->
54
55
case do_parse(Type, Data) of
55
55
-
{ok, T, Parsed} ->
56
56
-
{ok, Tag, T, Parsed};
56
56
+
{ok, Parsed} ->
57
57
+
{ok, Tag, Parsed};
57
58
{error, Reason} ->
58
59
{error, Reason}
59
60
end.
60
61
61
62
%% version - negotiate protocol version
62
63
do_parse(?Tversion, <<MSize:4/?int, VSize:?len, Version:VSize/binary>>) ->
63
63
-
{ok, tversion, #{max_packet_size => MSize, version => Version}};
64
64
+
{ok, #tversion{max_packet_size = MSize, version = Version}};
64
65
do_parse(?Rversion, <<MSize:4/?int, VSize:?len, Version:VSize/binary>>) ->
65
65
-
{ok, rversion, #{max_packet_size => MSize, version => Version}};
66
66
+
{ok, #rversion{max_packet_size = MSize, version = Version}};
66
67
67
68
%% attach, auth - messages to establish a connection
68
69
do_parse(?Tauth, <<AFID:4/?int,
69
70
UnameLen:?len, Uname:UnameLen/binary,
70
71
AnameLen:?len, Aname:AnameLen/binary>>) ->
71
71
-
{ok, tauth, #{afid => AFID,
72
72
-
uname => Uname,
73
73
-
aname => Aname}};
72
72
+
{ok, #tauth{afid = AFID,
73
73
+
uname = Uname,
74
74
+
aname = Aname}};
74
75
do_parse(?Rauth, <<AQID:13/binary>>) ->
75
75
-
{ok, rauth, #{aqid => binary_to_qid(AQID)}};
76
76
+
{ok, #rauth{aqid = binary_to_qid(AQID)}};
76
77
77
78
do_parse(?Tattach, <<FID:4/?int,
78
79
AFID:4/?int,
79
80
ULen:?len, Uname:ULen/binary,
80
81
ALen:?len, Aname:ALen/binary>>) ->
81
81
-
{ok, tattach, #{fid => FID,
82
82
-
afid => AFID,
83
83
-
uname => Uname,
84
84
-
aname => Aname}};
82
82
+
{ok, #tattach{fid = FID,
83
83
+
afid = AFID,
84
84
+
uname = Uname,
85
85
+
aname = Aname}};
85
86
do_parse(?Rattach, <<QID:13/binary>>) ->
86
86
-
{ok, rattach, #{qid => binary_to_qid(QID)}};
87
87
+
{ok, #rattach{qid = binary_to_qid(QID)}};
87
88
88
89
%% clunk - forget about a fid
89
90
do_parse(?Tclunk, <<FID:4/?int>>) ->
90
90
-
{ok, tclunk, #{fid => FID}};
91
91
+
{ok, #tclunk{fid = FID}};
91
92
do_parse(?Rclunk, <<>>) ->
92
92
-
{ok, rclunk, #{}};
93
93
+
{ok, #rclunk{}};
93
94
94
95
%% error - return an error
95
96
do_parse(?Rerror, <<ELen:?len, Error:ELen/binary>>) ->
96
96
-
{ok, rerror, #{error => Error}};
97
97
+
{ok, #rerror{msg = Error}};
97
98
98
99
%% flush - abort a message
99
100
do_parse(?Tflush, <<Tag:2/?int>>) ->
100
100
-
{ok, tflush, #{tag => Tag}};
101
101
+
{ok, #tflush{tag = Tag}};
101
102
do_parse(?Rflush, <<>>) ->
102
102
-
{ok, rflush, #{}};
103
103
+
{ok, #rflush{}};
103
104
104
105
%% open, create - prepare a fid for I/O on an existing or new file
105
106
do_parse(?Topen, <<FID:4/?int, Mode:1/?int>>) ->
106
106
-
{ok, topen, #{fid => FID, mode => Mode}};
107
107
+
{ok, #topen{fid = FID, mode = Mode}};
107
108
do_parse(?Ropen, <<QID:13/binary, IOUnit:4/?int>>) ->
108
108
-
{ok, ropen, #{qid => binary_to_qid(QID), io_unit => IOUnit}};
109
109
+
{ok, #ropen{qid = binary_to_qid(QID), io_unit = IOUnit}};
109
110
110
111
do_parse(?Tcreate, <<FID:4/?int,
111
112
NLen:?len, Name:NLen/binary,
112
113
Perm:4/?int,
113
114
Mode:1/?int>>) ->
114
114
-
{ok, tcreate, #{fid => FID, name => Name, perm => Perm, mode => Mode}};
115
115
+
{ok, #tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}};
115
116
do_parse(?Rcreate, <<QID:13/binary, IOUnit:4/?int>>) ->
116
116
-
{ok, rcreate, #{qid => binary_to_qid(QID), io_unit => IOUnit}};
117
117
+
{ok, #rcreate{qid = binary_to_qid(QID), io_unit = IOUnit}};
117
118
118
119
%% remove - remove a file from a server
119
120
do_parse(?Tremove, <<FID:4/?int>>) ->
120
120
-
{ok, tremove, #{fid => FID}};
121
121
+
{ok, #tremove{fid = FID}};
121
122
do_parse(?Rremove, <<>>) ->
122
122
-
{ok, rremove, #{}};
123
123
+
{ok, #rremove{}};
123
124
124
125
%% stat, wstat - inquire or change file attributes
125
126
do_parse(?Tstat, <<FID:4/?int>>) ->
126
126
-
{ok, tstat, #{fid => FID}};
127
127
+
{ok, #tstat{fid = FID}};
127
128
do_parse(?Rstat, <<DLen:?len, Data:DLen/binary>>) ->
128
129
case parse_stat(Data) of
129
130
{ok, Stat} ->
130
130
-
{ok, rstat, #{stat => Stat}};
131
131
+
{ok, #rstat{stat = Stat}};
131
132
132
133
{error, _} = Error ->
133
134
Error
···
136
137
do_parse(?Twstat, <<FID:4/?int, DLen:?len, Data:DLen/binary>>) ->
137
138
case parse_stat(Data) of
138
139
{ok, Stat} ->
139
139
-
{ok, twstat, #{fid => FID, stat => Stat}};
140
140
+
{ok, #twstat{fid = FID, stat = Stat}};
140
141
141
142
{error, _} = Error ->
142
143
Error
143
144
end;
144
145
do_parse(?Rwstat, <<>>) ->
145
145
-
{ok, rwstat, #{}};
146
146
+
{ok, #rwstat{}};
146
147
147
148
%% walk - descend a directory hierarchy
148
149
do_parse(?Twalk, <<FID:4/?int, NewFID:4/?int, NWNLen:?len, Rest/binary>>) ->
···
150
151
Len = length(NWNames),
151
152
if
152
153
Len == NWNLen ->
153
153
-
{ok, twalk, #{fid => FID, new_fid => NewFID, names => NWNames}};
154
154
+
{ok, #twalk{fid = FID, new_fid = NewFID, names = NWNames}};
154
155
true ->
155
156
{error, {invalid_walk_length, NWNLen, Len}}
156
157
end;
157
158
do_parse(?Rwalk, <<NWQLen:?len, QIDs:(NWQLen * 13)/binary>>) ->
158
158
-
{ok, rwalk, #{qids => [binary_to_qid(QID) || <<QID:13/binary>> <= QIDs]}};
159
159
+
{ok, #rwalk{qids = [binary_to_qid(QID) || <<QID:13/binary>> <= QIDs]}};
159
160
160
160
-
do_parse(?Tread, <<FID:4/?int, Offset:8/?int, Count:4/?int>>) ->
161
161
-
{ok, tread, #{fid => FID, offset => Offset, count => Count}};
161
161
+
do_parse(?Tread, <<FID:4/?int, Offset:8/?int, Len:4/?int>>) ->
162
162
+
{ok, #tread{fid = FID, offset = Offset, len = Len}};
162
163
do_parse(?Rread, <<Count:4/?int, Data:Count/?int>>) ->
163
163
-
{ok, rread, #{data => Data}};
164
164
+
{ok, #rread{data = Data}};
164
165
165
166
do_parse(Type, Data) ->
166
167
{error, {invalid_message, Type, Data}}.
···
193
194
}};
194
195
parse_stat(_) -> {error, invalid_stat_data}.
195
196
196
196
-
-spec encode(Tag :: tag() | notag, Type :: message_type(), Data :: map()) -> iodata().
197
197
-
encode(Tag, Type, Data) ->
198
198
-
{MT, Encoded} = do_encode(Type, Data),
197
197
+
-spec encode(Tag :: tag() | notag, Data :: message()) -> iodata().
198
198
+
encode(Tag, Data) ->
199
199
+
{MT, Encoded} = do_encode(Data),
199
200
Tag0 = case Tag of
200
201
notag -> ?notag;
201
202
V -> V
202
203
end,
203
204
[<<MT:1/?int, Tag0:2/?int>> | Encoded].
204
205
205
205
-
do_encode(tversion, #{max_packet_size := MSize, version := Version}) ->
206
206
+
do_encode(#tversion{max_packet_size = MSize, version = Version}) ->
206
207
{?Tversion, [<<MSize:4/?int>> | encode_str(Version)]};
207
207
-
do_encode(rversion, #{max_packet_size := MSize, version := Version}) ->
208
208
+
do_encode(#rversion{max_packet_size = MSize, version = Version}) ->
208
209
{?Rversion, [<<MSize:4/?int>> | encode_str(Version)]};
209
210
210
210
-
do_encode(tauth, #{afid := AFID, uname := Uname, aname := Aname}) ->
211
211
+
do_encode(#tauth{afid = AFID, uname = Uname, aname = Aname}) ->
211
212
{?Tauth, [<<AFID:4/?int>>, encode_str(Uname), encode_str(Aname)]};
212
212
-
do_encode(rauth, #{aqid := AQID}) ->
213
213
+
do_encode(#rauth{aqid = AQID}) ->
213
214
{?Rauth, qid_to_binary(AQID)};
214
215
215
215
-
do_encode(tattach, #{fid := FID, afid := AFID, uname := Uname, aname := Aname}) ->
216
216
+
do_encode(#tattach{fid = FID, afid = AFID, uname = Uname, aname = Aname}) ->
216
217
{?Tattach, [<<FID:4/?int, AFID:4/?int>>, encode_str(Uname), encode_str(Aname)]};
217
217
-
do_encode(rattach, #{qid := QID}) ->
218
218
+
do_encode(#rattach{qid = QID}) ->
218
219
{?Rattach, qid_to_binary(QID)};
219
220
220
220
-
do_encode(tclunk, #{fid := FID}) ->
221
221
+
do_encode(#tclunk{fid = FID}) ->
221
222
{?Tclunk, <<FID:4/?int>>};
222
222
-
do_encode(rclunk, _) ->
223
223
+
do_encode(#rclunk{}) ->
223
224
{?Rclunk, []};
224
225
225
225
-
do_encode(rerror, #{error := Error}) ->
226
226
+
do_encode(#rerror{msg = Error}) ->
226
227
{?Rerror, encode_str(Error)};
227
228
228
228
-
do_encode(tflush, #{tag := Tag}) ->
229
229
+
do_encode(#tflush{tag = Tag}) ->
229
230
{?Tflush, <<Tag:2/?int>>};
230
230
-
do_encode(rflush, _) ->
231
231
+
do_encode(#rflush{}) ->
231
232
{?Rflush, []};
232
233
233
233
-
do_encode(topen, #{fid := FID, mode := Mode}) ->
234
234
+
do_encode(#topen{fid = FID, mode = Mode}) ->
234
235
{?Topen, <<FID:4/?int, Mode:1/?int>>};
235
235
-
do_encode(ropen, #{qid := QID, io_unit := IOUnit}) ->
236
236
+
do_encode(#ropen{qid = QID, io_unit = IOUnit}) ->
236
237
{?Ropen, [qid_to_binary(QID), <<IOUnit:4/?int>>]};
237
238
238
238
-
do_encode(tcreate, #{fid := FID, name := Name, perm := Perm, mode := Mode}) ->
239
239
+
do_encode(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}) ->
239
240
{?Tcreate, [<<FID:4/?int>>, encode_str(Name), <<Perm:4/?int, Mode:1/?int>>]};
240
240
-
do_encode(rcreate, #{qid := QID, io_unit := IOUnit}) ->
241
241
+
do_encode(#rcreate{qid = QID, io_unit = IOUnit}) ->
241
242
{?Rcreate, [qid_to_binary(QID), <<IOUnit:4/?int>>]};
242
243
243
243
-
do_encode(tremove, #{fid := FID}) ->
244
244
+
do_encode(#tremove{fid = FID}) ->
244
245
{?Tremove, <<FID:4/?int>>};
245
245
-
do_encode(rremove, _) ->
246
246
+
do_encode(#rremove{}) ->
246
247
{?Rremove, []};
247
248
248
248
-
do_encode(tstat, #{fid := FID}) ->
249
249
+
do_encode(#tstat{fid = FID}) ->
249
250
{?Tstat, <<FID:4/?int>>};
250
250
-
do_encode(rstat, #{stat := Stat}) ->
251
251
+
do_encode(#rstat{stat = Stat}) ->
251
252
{?Rstat, encode_stat(Stat)};
252
253
253
253
-
do_encode(twstat, #{fid := FID, stat := Stat}) ->
254
254
+
do_encode(#twstat{fid = FID, stat = Stat}) ->
254
255
{?Twstat, [<<FID:4/?int>>, encode_stat(Stat)]};
255
255
-
do_encode(rwstat, _) ->
256
256
+
do_encode(#rwstat{}) ->
256
257
{?Rwstat, []};
257
258
258
258
-
do_encode(twalk, #{fid := FID, new_fid := NewFID, names := Names}) ->
259
259
+
do_encode(#twalk{fid = FID, new_fid = NewFID, names = Names}) ->
259
260
ENames = [encode_str(Name) || Name <- Names],
260
261
Len = length(ENames),
261
262
{?Twalk, [<<FID:4/?int, NewFID:4/?int, Len:?len>> | ENames]};
262
262
-
do_encode(rwalk, #{qids := QIDs}) ->
263
263
+
do_encode(#rwalk{qids = QIDs}) ->
263
264
EQIDs = [qid_to_binary(QID) || QID <- QIDs],
264
265
Len = length(EQIDs),
265
266
{?Rwalk, [<<Len:?len>> | EQIDs]};
266
267
267
267
-
do_encode(tread, #{fid := FID, offset := Offset, count := Count}) ->
268
268
-
{?Tread, <<FID:4/?int, Offset:8/?int, Count:4/?int>>};
269
269
-
do_encode(rread, #{data := Data}) ->
268
268
+
do_encode(#tread{fid = FID, offset = Offset, len = Len}) ->
269
269
+
{?Tread, <<FID:4/?int, Offset:8/?int, Len:4/?int>>};
270
270
+
do_encode(#rread{data = Data}) ->
270
271
{?Rread, encode_str(Data)}.
271
272
272
273
encode_stat(#{
+101
-43
src/e9p_server.erl
···
4
4
5
5
-module(e9p_server).
6
6
7
7
+
-include("e9p_internal.hrl").
8
8
+
7
9
-include_lib("kernel/include/logger.hrl").
8
10
9
11
-export([start_link/2,
···
26
28
accept_loop(LSock, Handler) ->
27
29
case gen_tcp:accept(LSock, 5000) of
28
30
{ok, Sock} ->
31
31
+
% TODO: Handle connected clients in separate process
29
32
ok = ?MODULE:loop(Sock, #{}, Handler),
30
33
?MODULE:accept_loop(LSock, Handler);
31
34
{error, timeout} ->
···
36
39
37
40
loop(Sock, FIDs, Handler) ->
38
41
case e9p_transport:read(Sock) of
39
39
-
{ok, Tag, Type, Data} ->
40
40
-
case handle_message(Type, Data, FIDs, Handler) of
41
41
-
{ok, {RType, RData}, RFIDs, RHandler} ->
42
42
-
e9p_transport:send(Sock, Tag, RType, RData),
43
43
-
?MODULE:loop(Sock, RFIDs, RHandler)
42
42
+
{ok, Tag, Data} ->
43
43
+
case handle_message(Data, FIDs, Handler) of
44
44
+
{ok, Reply, RFIDs, RHandler} ->
45
45
+
e9p_transport:send(Sock, Tag, Reply),
46
46
+
?MODULE:loop(Sock, RFIDs, RHandler);
47
47
+
{error, Err, RHandler} ->
48
48
+
e9p_transport:send(Sock, Tag, error_msg(Err)),
49
49
+
?MODULE:loop(Sock, FIDs, RHandler)
44
50
end;
45
51
{error, closed} ->
46
52
?LOG_WARNING("Connection closed"),
47
53
ok
48
54
end.
49
55
50
50
-
handle_message(tversion, #{version := ~"9P2000"} = Data, FIDs, Handler) ->
51
51
-
{ok, {rversion, Data}, FIDs, Handler};
52
52
-
handle_message(tattach, Data, FIDs, Handler0) ->
53
53
-
#{fid := FID, uname := _UName, aname := AName} = Data,
54
54
-
{ok, QID, Handler} = e9p_fs:root(Handler0, AName),
55
55
-
NFIDs = FIDs#{FID => QID},
56
56
-
{ok, {rattach, #{qid => QID}}, NFIDs, Handler};
57
57
-
handle_message(tclunk, #{fid := FID}, FIDs, Handler) ->
58
58
-
NFIDs = maps:remove(FID, FIDs),
59
59
-
{ok, {rflush, #{}}, NFIDs, Handler};
60
60
-
handle_message(twalk, Data, FIDs, Handler0) ->
61
61
-
#{
62
62
-
fid := FID,
63
63
-
new_fid := NewFID,
64
64
-
names := Paths
65
65
-
} = Data,
66
66
-
#{FID := QID} = FIDs,
67
67
-
{ok, NewQID, QIDs, Handler} = e9p_fs:walk(Handler0, QID, Paths),
68
68
-
{ok, {rwalk, #{qids => QIDs}}, FIDs#{NewFID => NewQID}, Handler};
69
69
-
handle_message(topen, #{fid := FID}, FIDs, Handler0) ->
70
70
-
#{FID := QID} = FIDs,
71
71
-
{ok, IOUnit, Handler} = e9p_fs:open(Handler0, QID),
72
72
-
{ok, {ropen, #{qid => QID, iounit => IOUnit}}, FIDs, Handler};
73
73
-
handle_message(tcreate, #{fid := FID}, FIDs, Handler0) ->
74
74
-
#{FID := QID, name := Name, perm := Perm, mode := Mode} = FIDs,
75
75
-
{ok, {NewQID, IOUnit}, Handler} = e9p_fs:create(Handler0, QID, Name, Perm, Mode),
76
76
-
{ok, {rcreate, #{qid => NewQID, iounit => IOUnit}}, FIDs, Handler};
77
77
-
handle_message(tread, Data, FIDs, Handler0) ->
78
78
-
#{
79
79
-
fid := FID,
80
80
-
offset := Offset,
81
81
-
count := Count
82
82
-
} = Data,
83
83
-
#{FID := QID} = FIDs,
84
84
-
{ok, Data, Handler} = e9p_fs:read(Handler0, QID, Offset, Count),
85
85
-
{ok, {rread, #{data => Data}}, FIDs, Handler};
86
86
-
handle_message(_Type, _Data, FIDs, Handler) ->
87
87
-
{ok, {rerror, #{error => ~"Unknown request type"}}, FIDs, Handler}.
56
56
+
handle_message(#tversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler) ->
57
57
+
% Currently only "basic" 9p2000 version is supported, without any extensions
58
58
+
% like `.u` or `.L`
59
59
+
{ok, #rversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler};
60
60
+
61
61
+
handle_message(#tflush{}, FIDs, Handler) ->
62
62
+
% Currently there is no support for parallel messages, so this does simply
63
63
+
% nothing
64
64
+
{ok, #rflush{}, FIDs, Handler};
65
65
+
66
66
+
handle_message(#tattach{fid = FID, uname = _UName, aname = AName}, FIDs, Handler0) ->
67
67
+
maybe
68
68
+
{ok, QID, Handler} ?= e9p_fs:root(Handler0, AName),
69
69
+
NFIDs = FIDs#{FID => QID},
70
70
+
{ok, #rattach{qid = QID}, NFIDs, Handler}
71
71
+
end;
72
72
+
73
73
+
handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) ->
74
74
+
maybe
75
75
+
{ok, QID} ?= get_qid(FIDs, FID),
76
76
+
{ok, NewQID, QIDs, Handler} ?= e9p_fs:walk(Handler0, QID, Paths),
77
77
+
{ok, #rwalk{qids = QIDs}, FIDs#{NewFID => NewQID}, Handler}
78
78
+
end;
79
79
+
80
80
+
handle_message(#topen{fid = FID}, FIDs, Handler0) ->
81
81
+
maybe
82
82
+
{ok, QID} ?= get_qid(FIDs, FID),
83
83
+
{ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID),
84
84
+
{ok, #ropen{qid = QID, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler}
85
85
+
end;
86
86
+
handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) ->
87
87
+
maybe
88
88
+
{ok, QID} ?= get_qid(FIDs, FID),
89
89
+
{ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:create(Handler0, QID, Name, Perm, Mode),
90
90
+
{ok, #rcreate{qid = NewQID, io_unit = IOUnit}, FIDs, Handler}
91
91
+
end;
92
92
+
93
93
+
handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) ->
94
94
+
maybe
95
95
+
{ok, QID} ?= get_qid(FIDs, FID),
96
96
+
{ok, Data, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len),
97
97
+
{ok, #rread{data = Data}, FIDs, Handler}
98
98
+
end;
99
99
+
handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) ->
100
100
+
maybe
101
101
+
{ok, QID} ?= get_qid(FIDs, FID),
102
102
+
{ok, Data, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data),
103
103
+
{ok, #rread{data = Data}, FIDs, Handler}
104
104
+
end;
105
105
+
106
106
+
handle_message(#tclunk{fid = FID}, FIDs, Handler0) ->
107
107
+
maybe
108
108
+
{ok, QID} ?= get_qid(FIDs, FID),
109
109
+
{ok, Handler} ?= e9p_fs:clunk(Handler0, QID),
110
110
+
NFIDs = maps:remove(FID, FIDs),
111
111
+
{ok, #rclunk{}, NFIDs, Handler}
112
112
+
end;
113
113
+
114
114
+
handle_message(#tremove{fid = FID}, FIDs, Handler0) ->
115
115
+
maybe
116
116
+
{ok, QID} ?= get_qid(FIDs, FID),
117
117
+
{ok, Handler} ?= e9p_fs:remove(Handler0, QID),
118
118
+
{ok, #rremove{}, FIDs, Handler}
119
119
+
end;
120
120
+
121
121
+
handle_message(#tstat{fid = FID}, FIDs, Handler0) ->
122
122
+
maybe
123
123
+
{ok, QID} ?= get_qid(FIDs, FID),
124
124
+
{ok, Stat, Handler} ?= e9p_fs:stat(Handler0, QID),
125
125
+
{ok, #rstat{stat = Stat}, FIDs, Handler}
126
126
+
end;
127
127
+
128
128
+
handle_message(#twstat{fid = FID, stat = Stat}, FIDs, Handler0) ->
129
129
+
maybe
130
130
+
{ok, QID} ?= get_qid(FIDs, FID),
131
131
+
{ok, Handler} ?= e9p_fs:wstat(Handler0, QID, Stat),
132
132
+
{ok, #rwstat{}, FIDs, Handler}
133
133
+
end;
134
134
+
135
135
+
handle_message(_Msg, _FIDs, Handler) ->
136
136
+
{error, ~"Unknown request type", Handler}.
137
137
+
138
138
+
get_qid(FIDs, FID) ->
139
139
+
case FIDs of
140
140
+
#{FID := QID} -> {ok, QID};
141
141
+
_ -> {error, io_lib:fwrite(~"Unknown FID: ~B", [FID])}
142
142
+
end.
143
143
+
144
144
+
error_msg(Data) ->
145
145
+
#rerror{msg = io_lib:fwrite(~"~p", [Data])}.
+7
-7
src/e9p_transport.erl
···
6
6
7
7
-include("e9p_internal.hrl").
8
8
9
9
-
-export([send/4, read/1, read_stream/1]).
9
9
+
-export([send/3, read/1, read_stream/1]).
10
10
11
11
-
send(Socket, Tag, Type, Message) ->
12
12
-
Encoded = e9p_msg:encode(Tag, Type, Message),
11
11
+
send(Socket, Tag, Message) ->
12
12
+
Encoded = e9p_msg:encode(Tag, Message),
13
13
Size = iolist_size(Encoded) + 4,
14
14
gen_tcp:send(Socket, [<<Size:4/?int>>, Encoded]).
15
15
···
19
19
case gen_tcp:recv(Socket, Size - 4) of
20
20
{ok, Data} when is_binary(Data) ->
21
21
case e9p_msg:parse(Data) of
22
22
-
{ok, Tag, Type, Msg} ->
23
23
-
{ok, Tag, Type, Msg};
22
22
+
{ok, Tag, Msg} ->
23
23
+
{ok, Tag, Msg};
24
24
{error, _} = Error ->
25
25
Error
26
26
end;
···
33
33
34
34
read_stream(<<Size:4/?int, Data:(Size - 4)/binary, Rest/binary>> = Input) ->
35
35
case e9p_msg:parse(Data) of
36
36
-
{ok, Tag, Type, Msg} ->
37
37
-
{ok, Tag, Type, Msg, Rest};
36
36
+
{ok, Tag, Msg} ->
37
37
+
{ok, Tag, Msg, Rest};
38
38
{error, Error} ->
39
39
{error, Error, Input}
40
40
end;
+33
-5
src/e9p_unfs.erl
···
8
8
9
9
-include_lib("kernel/include/file.hrl").
10
10
11
11
-
-export([init/1, root/2, walk/3, stat/2, read/4, write/4]).
11
11
+
-export([init/1, root/2, walk/3, stat/2, open/2, read/4]).
12
12
13
13
init(#{path := Path}) ->
14
14
{ok, #{root => Path}}.
···
21
21
Next = filename:join(Path, File),
22
22
case file:read_file_info(Next, [{time, posix}]) of
23
23
{ok, #file_info{type = Type, inode = Inode}} ->
24
24
-
NQid = e9p:make_qid(Type, 0, Inode, Next),
24
24
+
NQid = e9p:make_qid(Type, 0, Inode, {Next, []}),
25
25
{NQid, State};
26
26
27
27
{error, _} ->
28
28
{false, State}
29
29
end.
30
30
31
31
-
stat(_Qid, State) ->
31
31
+
stat(#{state := {Path, []}} = QID, State) ->
32
32
+
case file:read_file_info(Path, [{time, posix}]) of
33
33
+
{ok, FileInfo} ->
34
34
+
Stat = file_info_to_stat(QID, FileInfo),
35
35
+
{ok, Stat, State};
36
36
+
{error, Error} ->
37
37
+
{error, Error, State}
38
38
+
end.
39
39
+
40
40
+
open(_Qid, State) ->
32
41
{error, unimplemented, State}.
33
42
34
43
read(_Qid, _Offset, _Len, State) ->
35
44
{error, unimplemented, State}.
36
45
37
37
-
write(_Qid, _Offset, _Data, State) ->
38
38
-
{error, unimplemented, State}.
46
46
+
file_info_to_stat(
47
47
+
#{state := Path} = QID,
48
48
+
#file_info{
49
49
+
size = Len,
50
50
+
atime = Atime,
51
51
+
mtime = Mtime,
52
52
+
mode = Mode
53
53
+
}) ->
54
54
+
#{
55
55
+
type => 0,
56
56
+
dev => 0,
57
57
+
qid => QID,
58
58
+
mode => Mode,
59
59
+
atime => Atime,
60
60
+
mtime => Mtime,
61
61
+
length => Len,
62
62
+
name => filename:basename(Path),
63
63
+
uid => ~"",
64
64
+
gid => ~"",
65
65
+
muid => ~""
66
66
+
}.
+16
-9
src/e9p_utils.erl
···
4
4
5
5
-module(e9p_utils).
6
6
7
7
-
-export([normalize_path/1, qtype_from_atom/1]).
7
7
+
-export([normalize_path/1, to_qtype/1]).
8
8
9
9
normalize_path(List) -> normalize_path(List, []).
10
10
···
16
16
normalize_path([P | Rest], Acc) ->
17
17
normalize_path(Rest, [P | Acc]).
18
18
19
19
-
qtype_from_atom(dir) -> 16#80;
20
20
-
qtype_from_atom(append) -> 16#40;
21
21
-
qtype_from_atom(excl) -> 16#20;
22
22
-
qtype_from_atom(device) -> 16#10;
23
23
-
qtype_from_atom(auth) -> 16#08;
24
24
-
qtype_from_atom(tmp) -> 16#04;
25
25
-
qtype_from_atom(symlink) -> 16#02;
26
26
-
qtype_from_atom(regular) -> 16#00.
19
19
+
to_qtype(List) when is_list(List) ->
20
20
+
lists:foldl(
21
21
+
fun(El, Acc) when is_integer(Acc) -> to_qtype(El) bor Acc end,
22
22
+
0,
23
23
+
List
24
24
+
);
25
25
+
26
26
+
to_qtype(dir) -> 16#80;
27
27
+
to_qtype(append) -> 16#40;
28
28
+
to_qtype(excl) -> 16#20;
29
29
+
to_qtype(device) -> 16#10;
30
30
+
to_qtype(auth) -> 16#08;
31
31
+
to_qtype(tmp) -> 16#04;
32
32
+
to_qtype(symlink) -> 16#02;
33
33
+
to_qtype(regular) -> 16#00.
+27
-5
test/prop_e9p_msg.erl
···
4
4
5
5
-module(prop_e9p_msg).
6
6
7
7
+
-include("e9p_internal.hrl").
7
8
-include_lib("proper/include/proper.hrl").
8
9
% -include_lib("stdlib/include/assert.hrl").
9
10
11
11
+
afid() -> integer(16#0000, 16#FFFF).
12
12
+
13
13
+
prop_can_decode_encoded_tversion() ->
14
14
+
?FORALL({Version, MPS}, {binary(), afid()},
15
15
+
begin
16
16
+
enc_dec(#tversion{version = Version, max_packet_size = MPS})
17
17
+
end).
18
18
+
19
19
+
prop_can_decode_encoded_rversion() ->
20
20
+
?FORALL({Version, MPS}, {binary(), afid()},
21
21
+
begin
22
22
+
enc_dec(#rversion{version = Version, max_packet_size = MPS})
23
23
+
end).
24
24
+
10
25
prop_can_decode_encoded_tauth() ->
11
11
-
?FORALL({Uname, Aname}, {binary(), binary()},
26
26
+
?FORALL({Afid, Uname, Aname}, {integer(0, 16#FFFF), binary(), binary()},
12
27
begin
13
13
-
enc_dec(tauth, #{afid => 1, uname => Uname, aname => Aname})
28
28
+
enc_dec(#tauth{afid = Afid, uname = Uname, aname = Aname})
14
29
end).
15
30
16
16
-
enc_dec(Kind, Data) ->
31
31
+
prop_can_decode_encoded_rerror() ->
32
32
+
?FORALL({Msg}, {binary()},
33
33
+
begin
34
34
+
enc_dec(#rerror{msg = Msg})
35
35
+
end).
36
36
+
37
37
+
38
38
+
enc_dec(Data) ->
17
39
Tag = 1,
18
18
-
Out = e9p_msg:encode(Tag, Kind, Data),
40
40
+
Out = e9p_msg:encode(Tag, Data),
19
41
Encoded = iolist_to_binary(Out),
20
20
-
{ok, Tag, Kind, Data} =:= e9p_msg:parse(Encoded).
42
42
+
{ok, Tag, Data} =:= e9p_msg:parse(Encoded).