diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -20,16 +20,18 @@ + [ ] File/directory deletion + [ ] File stats - [ ] Server implementation + [x] Establishing connection - + [ ] Tree walking + + [x] Tree walking + [ ] File/directory creation + [ ] File/directory deletion - + [ ] File stats + + [x] File stats + [ ] Customisable FS implementations ### Example FS -- [ ] "Passthrough" - which will simply allow accessing some "real" directory in +- [-] UnixFs - which will simply allow accessing some "real" directory in system FS + + **WIP**: Implemented directory reading and file reading. - [ ] ErlProcFS - which will expose Erlang process tree and other internal data via API similar to `procfs` from Linux diff --git a/src/e9p.erl b/src/e9p.erl --- a/src/e9p.erl +++ b/src/e9p.erl @@ -4,7 +4,7 @@ % SPDX-License-Identifier: Apache-2.0 -module(e9p). --export([make_qid/4]). +-export([make_qid/4, is_type/2]). -export_type([qid/0, fid/0]). @@ -27,8 +27,27 @@ %-spec make_qid() make_qid(Type, Version, Path, State) -> #{ - type => e9p_utils:to_qtype(Type), + type => to_qtype(Type), version => Version, path => Path, state => State }. + +is_type(#{type := QType}, Type) -> + (to_qtype(Type) band QType) =/= 0. + +to_qtype(List) when is_list(List) -> + lists:foldl( + fun(El, Acc) when is_integer(Acc) -> to_qtype(El) bor Acc end, + 0, + List + ); + +to_qtype(directory) -> 16#80; +to_qtype(append) -> 16#40; +to_qtype(excl) -> 16#20; +to_qtype(device) -> 16#10; +to_qtype(auth) -> 16#08; +to_qtype(tmp) -> 16#04; +to_qtype(symlink) -> 16#02; +to_qtype(regular) -> 16#00. diff --git a/src/e9p_fs.erl b/src/e9p_fs.erl --- a/src/e9p_fs.erl +++ b/src/e9p_fs.erl @@ -10,7 +10,7 @@ -export([ init/1, root/2, walk/3, - open/2, + open/3, create/5, read/4, write/4, @@ -30,7 +30,7 @@ -define(if_supported(Code), case erlang:function_exported(Mod, ?FUNCTION_NAME, ?FUNCTION_ARITY) of true -> case (fun() -> Code end)() of - {ok, Ret, {Mod, NewState}} -> + {ok, Ret, NewState} -> {ok, Ret, {Mod, NewState}}; {error, Error, NewState} -> {error, Error, {Mod, NewState}} @@ -56,7 +56,7 @@ %% Walk through the given path starting at the `QID' -callback walk(QID :: e9p:qid(), unicode:chardata(), state()) -> {e9p:qid() | false, state()}. --callback open(QID :: e9p:qid(), state()) -> result({e9p:qid(), e9p:u32()}). +-callback open(QID :: e9p:qid(), Mode :: integer(), state()) -> result({e9p:qid(), e9p:u32()}). -callback create(QID :: e9p:qid(), Name :: unicode:chardata(), @@ -68,13 +68,13 @@ %% Read data from file indicated by `QID' -callback read(QID :: e9p:qid(), Offset :: non_neg_integer(), Length :: non_neg_integer(), - state()) -> result(iodata()). + state()) -> result({e9p:qid(), iodata()}). %% Write data to file indicated by `QID' -callback write(QID :: e9p:qid(), Offset :: non_neg_integer(), Data :: iodata(), - state()) -> result(non_neg_integer()). + state()) -> result({e9p:qid(), non_neg_integer()}). -callback clunk(QID :: e9p:qid(), state()) -> result(). @@ -89,7 +89,7 @@ -optional_callbacks([ flush/1, walk/3, - open/2, + open/3, create/5, read/4, write/4, @@ -117,18 +117,18 @@ """. walk({Mod, State}, QID, Paths) when is_atom(Mod) -> ?if_supported(do_walk(Mod, QID, Paths, State, [])). -do_walk(Mod, QID, [], State, Acc) -> - {ok, QID, lists:reverse(Acc), {Mod, State}}; +do_walk(_Mod, QID, [], State, Acc) -> + {ok, {QID, lists:reverse(Acc)}, State}; do_walk(Mod, QID0, [P | Rest], State0, Acc) -> case Mod:walk(QID0, P, State0) of {false, State} -> - {ok, QID0, lists:reverse(Acc), {Mod, State}}; + {ok, {QID0, lists:reverse(Acc)}, State}; {QID, State} -> do_walk(Mod, QID, Rest, State, [QID | Acc]) end. -open({Mod, State}, QID) -> - ?if_supported(Mod:open(QID, State)). +open({Mod, State}, QID, Mode) -> + ?if_supported(Mod:open(QID, Mode, State)). create({Mod, State}, QID, Name, Perm, Mode) -> ?if_supported(Mod:create(QID, Name, Perm, Mode, State)). @@ -139,12 +139,17 @@ write({Mod, State}, QID, Offset, Data) -> ?if_supported(Mod:write(QID, Offset, Data, State)). -clunk({Mod, State}, QID) -> +clunk({Mod, State0}, QID) -> case erlang:function_exported(Mod, clunk, 3) of true -> - {Resp, State} = Mod:clunk(QID, State), - {Resp, {Mod, State}}; - false -> {ok, {Mod, State}} + maybe + {ok, State} ?= Mod:clunk(QID, State0), + {ok, {Mod, State}} + else + {error, Reason, StateE} -> + {error, Reason, {Mod, StateE}} + end; + false -> {ok, {Mod, State0}} end. remove({Mod, State}, QID) -> diff --git a/src/e9p_msg.erl b/src/e9p_msg.erl --- a/src/e9p_msg.erl +++ b/src/e9p_msg.erl @@ -248,10 +248,14 @@ do_encode(#tstat{fid = FID}) -> {?Tstat, <>}; do_encode(#rstat{stat = Stat}) -> - {?Rstat, encode_stat(Stat)}; + Encoded = encode_stat(Stat), + Len = iolist_size(Encoded), + {?Rstat, [<> | encode_stat(Stat)]}; do_encode(#twstat{fid = FID, stat = Stat}) -> - {?Twstat, [<>, encode_stat(Stat)]}; + Encoded = encode_stat(Stat), + Len = iolist_size(Encoded), + {?Twstat, [<> | encode_stat(Stat)]}; do_encode(#rwstat{}) -> {?Rwstat, []}; @@ -267,7 +271,8 @@ do_encode(#tread{fid = FID, offset = Offset, len = Len}) -> {?Tread, <>}; do_encode(#rread{data = Data}) -> - {?Rread, encode_str(Data)}. + Len = iolist_size(Data), + {?Rread, [<>, Data]}. encode_stat(#{ type := Type, @@ -296,12 +301,14 @@ encode_str(Uid), encode_str(Gid), encode_str(MUid) ], - encode_str(Encoded). + ELen = iolist_size(Encoded), + [<> | Encoded]. %% ========== Utilities ========== -encode_str(Data) -> +encode_str(Data0) -> + Data = unicode:characters_to_binary(Data0), Len = iolist_size(Data), [<> | Data]. diff --git a/src/e9p_server.erl b/src/e9p_server.erl --- a/src/e9p_server.erl +++ b/src/e9p_server.erl @@ -40,6 +40,7 @@ loop(Sock, FIDs, Handler) -> case e9p_transport:read(Sock) of {ok, Tag, Data} -> + ?LOG_WARNING(#{msg => Data}), case handle_message(Data, FIDs, Handler) of {ok, Reply, RFIDs, RHandler} -> e9p_transport:send(Sock, Tag, Reply), @@ -73,14 +74,15 @@ handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) -> maybe {ok, QID} ?= get_qid(FIDs, FID), - {ok, NewQID, QIDs, Handler} ?= e9p_fs:walk(Handler0, QID, Paths), + {ok, {NewQID, QIDs}, Handler} ?= e9p_fs:walk(Handler0, QID, Paths), {ok, #rwalk{qids = QIDs}, FIDs#{NewFID => NewQID}, Handler} end; -handle_message(#topen{fid = FID}, FIDs, Handler0) -> +handle_message(#topen{fid = FID, mode = Mode}, FIDs, Handler0) -> maybe + ?LOG_WARNING(#{fids => FIDs}), {ok, QID} ?= get_qid(FIDs, FID), - {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID), + {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID, Mode), {ok, #ropen{qid = QID, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} end; handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) -> @@ -93,14 +95,14 @@ handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> maybe {ok, QID} ?= get_qid(FIDs, FID), - {ok, Data, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len), - {ok, #rread{data = Data}, FIDs, Handler} + {ok, {NQID, Data}, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len), + {ok, #rread{data = Data}, FIDs#{FID => NQID}, Handler} end; handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) -> maybe {ok, QID} ?= get_qid(FIDs, FID), - {ok, Data, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), - {ok, #rread{data = Data}, FIDs, Handler} + {ok, {NQID, Data}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), + {ok, #rread{data = Data}, FIDs#{FID => NQID}, Handler} end; handle_message(#tclunk{fid = FID}, FIDs, Handler0) -> diff --git a/src/e9p_transport.erl b/src/e9p_transport.erl --- a/src/e9p_transport.erl +++ b/src/e9p_transport.erl @@ -6,6 +6,8 @@ -module(e9p_transport). -include("e9p_internal.hrl"). +% -include_lib("kernel/include/logger.hrl"). + -export([send/3, read/1, read_stream/1]). send(Socket, Tag, Message) -> diff --git a/src/e9p_unfs.erl b/src/e9p_unfs.erl --- a/src/e9p_unfs.erl +++ b/src/e9p_unfs.erl @@ -6,51 +6,105 @@ -module(e9p_unfs). -behaviour(e9p_fs). +% -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/include/file.hrl"). --export([init/1, root/2, walk/3, stat/2, open/2, read/4]). +-export([init/1, root/2, walk/3, stat/2, open/3, read/4, clunk/2]). + +qid(Path) -> + case file:read_file_info(Path, [{time, posix}]) of + {ok, #file_info{type = Type, inode = Inode}} -> + NQid = e9p:make_qid(Type, 0, Inode, {Path, []}), + {ok, NQid}; + {error, _} = Error -> Error + end. + +qid_stat(Root, Path) -> + case file:read_file_info(Path, [{time, posix}]) of + {ok, #file_info{type = Type, inode = Inode} = FI} -> + NQid = e9p:make_qid(Type, 0, Inode, {Path, []}), + Stat = file_info_to_stat(Root, NQid, FI), + {ok, NQid, Stat}; + {error, _} = Error -> Error + end. init(#{path := Path}) -> {ok, #{root => Path}}. root(_AName, #{root := Root} = State) -> - Qid = e9p:make_qid(dir, 0, 0, Root), - {ok, Qid, State}. + maybe + {ok, Qid} ?= qid(Root), + {ok, Qid, State} + end. -walk(#{state := Path}, File, State) -> +walk(#{state := {Path, _}}, File, State) -> Next = filename:join(Path, File), - case file:read_file_info(Next, [{time, posix}]) of - {ok, #file_info{type = Type, inode = Inode}} -> - NQid = e9p:make_qid(Type, 0, Inode, {Next, []}), - {NQid, State}; - - {error, _} -> - {false, State} + case qid(Next) of + {ok, NQid} -> {NQid, State}; + {error, _} -> {false, State} end. -stat(#{state := {Path, []}} = QID, State) -> +stat(#{state := {Path, _}} = QID, #{root := Root} = State) -> case file:read_file_info(Path, [{time, posix}]) of {ok, FileInfo} -> - Stat = file_info_to_stat(QID, FileInfo), + Stat = file_info_to_stat(Root, QID, FileInfo), {ok, Stat, State}; {error, Error} -> {error, Error, State} end. -open(_Qid, State) -> - {error, unimplemented, State}. +open(#{state := {Path, []}} = QID, _Mode, State) -> + QS = case e9p:is_type(QID, directory) of + true -> + {ok, List} = file:list_dir(Path), + {dir, List}; + false -> + {ok, FD} = file:open(Path, [raw, read, binary]), + {regular, FD} + end, + NQID = QID#{state => {Path, QS}}, + {ok, {NQID, 0}, State}. -read(_Qid, _Offset, _Len, State) -> - {error, unimplemented, State}. +read(#{state := {_, {regular, FD}}} = QID, Offset, Len, State) -> + case file:pread(FD, Offset, Len) of + {ok, Data} -> {ok, {QID, Data}, State}; + eof -> {ok, {QID, []}, State}; + {error, Err} -> {error, Err, State} + end; +read(#{state := {Path, {dir, List}}} = QID, _Offset, Len, #{root := Root} = State) -> + {Remaining, Data} = readdir(Root, Path, List, Len, []), + {ok, {QID#{state => {Path, {dir, Remaining}}}, Data}, State}. + +readdir(_Root, _Path, List, 0, Acc) -> {List, Acc}; +readdir(_Root, _Path, [], _Len, Acc) -> {[], Acc}; +readdir(Root, Path, [Next | Rest], Len, Acc) -> + {ok, _QID, Stat} = qid_stat(Root, filename:join(Path, Next)), + Encoded = e9p_msg:encode_stat(Stat), + Size = iolist_size(Encoded), + if + Size > Len -> []; + true -> readdir(Root, Path, Rest, Len - Size, [Encoded | Acc]) + end. + +clunk(#{state := {_Path, {refular, FD}}}, State) -> + ok = file:close(FD), + {ok, State}; +clunk(_QID, State) -> + {ok, State}. file_info_to_stat( - #{state := Path} = QID, + Root, + #{state := {Path, _}} = QID, #file_info{ size = Len, atime = Atime, mtime = Mtime, mode = Mode }) -> + Name = if + Root == Path -> ~"/"; + true -> filename:basename(Path) + end, #{ type => 0, dev => 0, @@ -59,7 +113,7 @@ mode => Mode, atime => Atime, mtime => Mtime, length => Len, - name => filename:basename(Path), + name => Name, uid => ~"", gid => ~"", muid => ~"" diff --git a/src/e9p_utils.erl b/src/e9p_utils.erl --- a/src/e9p_utils.erl +++ b/src/e9p_utils.erl @@ -4,7 +4,7 @@ % SPDX-License-Identifier: Apache-2.0 -module(e9p_utils). --export([normalize_path/1, to_qtype/1]). +-export([normalize_path/1]). normalize_path(List) -> normalize_path(List, []). @@ -15,19 +15,3 @@ -> normalize_path(Rest, Acc); normalize_path([P | Rest], Acc) -> normalize_path(Rest, [P | Acc]). - -to_qtype(List) when is_list(List) -> - lists:foldl( - fun(El, Acc) when is_integer(Acc) -> to_qtype(El) bor Acc end, - 0, - List - ); - -to_qtype(dir) -> 16#80; -to_qtype(append) -> 16#40; -to_qtype(excl) -> 16#20; -to_qtype(device) -> 16#10; -to_qtype(auth) -> 16#08; -to_qtype(tmp) -> 16#04; -to_qtype(symlink) -> 16#02; -to_qtype(regular) -> 16#00.