From 4d2f54c4f355b6efe3f361b65f9a1b0d6bb41f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Niemier?= <~@hauleth.dev> Date: Thu, 22 Jan 2026 10:53:35 +0100 Subject: [PATCH] Rewrite e9p_fs implementation to do not allow QID modification This makes implementing new filesystems easier as author do not need to be concerned about FIDs, QIDs, and stuff, instead it will be handled for them in most cases. --- src/e9p.erl | 31 +++----- src/e9p_fs.erl | 163 ++++++++++++++++++++++++++----------------- src/e9p_internal.hrl | 3 + src/e9p_msg.erl | 15 +++- src/e9p_server.erl | 13 ++-- src/e9p_unfs.erl | 138 +++++++++++++++++++++++------------- 6 files changed, 218 insertions(+), 145 deletions(-) diff --git a/src/e9p.erl b/src/e9p.erl index 1064382..4cdd48a 100644 --- a/src/e9p.erl +++ b/src/e9p.erl @@ -4,44 +4,29 @@ -module(e9p). --export([make_qid/4, is_type/2]). +-export([make_qid/3, is_type/2]). -export_type([qid/0, fid/0]). -export_type([u8/0, u16/0, u32/0, u64/0]). +-include("e9p_internal.hrl"). + -type u8() :: 16#00..16#FF. -type u16() :: 16#0000..16#FFFF. -type u32() :: 16#00000000..16#FFFFFFFF. -type u64() :: 16#0000000000000000..16#FFFFFFFFFFFFFFFF. --type qid() :: #{ - type => u8(), - version => u16(), - path => u64(), - state => term() - }. +-opaque qid() :: #qid{type :: u8(), version :: u32(), path :: u64()}. -type fid() :: 16#00000000..16#FFFFFFFF. %-spec make_qid() -make_qid(Type, Version, Path, State) -> - #{ - type => to_qtype(Type), - version => Version, - path => Path, - state => State - }. - -is_type(#{type := QType}, Type) -> - (to_qtype(Type) band QType) =/= 0. +make_qid(Type, Version, Path) -> + #qid{type = to_qtype(Type), version = Version, path = Path}. -to_qtype(List) when is_list(List) -> - lists:foldl( - fun(El, Acc) when is_integer(Acc) -> to_qtype(El) bor Acc end, - 0, - List - ); +is_type(#qid{type = QType}, Type) -> + (to_qtype(Type) band QType) =/= 0. to_qtype(directory) -> 16#80; to_qtype(append) -> 16#40; diff --git a/src/e9p_fs.erl b/src/e9p_fs.erl index a3a11a9..0fef2e0 100644 --- a/src/e9p_fs.erl +++ b/src/e9p_fs.erl @@ -20,9 +20,15 @@ wstat/3 ]). --export_type([state/0]). +-include("e9p_internal.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-export_type([state/0, fid/0, path/0, result/0, result/1]). -type state() :: term(). +-type fid() :: {QID :: e9p:qid(), State :: fid_state()}. +-type path() :: [unicode:chardata()]. +-type fid_state() :: term(). -type result() :: {ok, state()} | {error, term(), state()}. -type result(T) :: {ok, T, state()} | {error, term(), state()}. @@ -48,55 +54,51 @@ %% If implementation provides multiple trees then the `AName' will be set to the %% tree defined by the client. It is left to the implementation to ensure the %% constraints of the file root (aka `walk(Root, "..", State0) =:= {Root, State1}'. --callback root(UName :: unicode:chardata(), AName :: unicode:chardata(), state()) -> {ok, e9p:qid(), state()}. +-callback root(UName :: unicode:chardata(), AName :: unicode:chardata(), state()) -> + {ok, fid_state(), state()}. --callback flush(state()) -> {ok, state()} | {error, term(), state()}. +-callback flush(state()) -> result(). %% Walk through the given path starting at the `QID' --callback walk(QID :: e9p:qid(), unicode:chardata(), state()) -> - {e9p:qid() | false, state()}. +-callback walk(fid(), File :: unicode:chardata(), unicode:chardata(), state()) -> + {fid() | false, state()}. --callback open(QID :: e9p:qid(), Mode :: integer(), state()) -> result({e9p:qid(), e9p:u32()}). +-callback open(fid(), path(), Mode :: integer(), state()) -> result({fid_state(), e9p:u32()}). --callback create(QID :: e9p:qid(), +-callback create(fid(), + path(), Name :: unicode:chardata(), Perm :: e9p:u32(), Mode :: e9p:u8(), - state()) -> result({e9p:qid(), e9p:u32()}). + state()) -> result({fid(), IOUnit :: e9p:u32()}). %% Read data from file indicated by `QID' --callback read(QID :: e9p:qid(), +-callback read(fid(), + path(), Offset :: non_neg_integer(), Length :: non_neg_integer(), - state()) -> result({e9p:qid(), iodata()}). + state()) -> result({fid_state(), iodata()}). %% Write data to file indicated by `QID' --callback write(QID :: e9p:qid(), +-callback write(fid(), + path(), Offset :: non_neg_integer(), Data :: iodata(), - state()) -> result({e9p:qid(), non_neg_integer()}). + state()) -> result({fid_state(), non_neg_integer()}). --callback clunk(QID :: e9p:qid(), state()) -> result(). +-callback clunk(fid(), path(), state()) -> result(). --callback remove(QID :: e9p:qid(), state()) -> result(). +-callback remove(fid(), path(), state()) -> result(). %% Return stat data for file indicated by `QID' --callback stat(QID :: e9p:qid(), state()) -> result(map()). +-callback stat(fid(), path(), state()) -> result(map()). %% Write stat data for file indicated by `QID' --callback wstat(QID :: e9p:qid(), map(), state()) -> result(). +-callback wstat(fid(), path(), map(), state()) -> result(). -optional_callbacks([ flush/1, - walk/3, - open/3, - create/5, - read/4, - write/4, - clunk/2, - remove/2, - stat/2, - wstat/3 + clunk/3 ]). init({Mod, State}) -> @@ -107,61 +109,94 @@ init({Mod, State}) -> root({Mod, State}, UName, AName) -> case Mod:root(UName, AName, State) of - {ok, QID, NewState} -> - {ok, QID, {Mod, NewState}} + {ok, {QID, FState}, NewState} -> + {ok, #fid{qid = QID, path = [], state = FState}, {Mod, NewState}} end. -doc """ Walk through paths starting at QID. """. -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)}, State}; -do_walk(Mod, QID0, [P | Rest], State0, Acc) -> - case Mod:walk(QID0, P, State0) of - {false, State} when Acc =:= [] -> - % Per specification walk to first entry in name list must succeed - % (if any) otherwise return error. In subsequent steps we return - % successful list and last succeeded QID - {error, io_lib:format("Failed walk to ~p", [P]), State}; - {false, State} -> - {ok, {QID0, lists:reverse(Acc)}, State}; - {QID, State} -> - do_walk(Mod, QID, Rest, State, [QID | Acc]) +walk({Mod, State0}, FID0, Paths) when is_atom(Mod) -> + case do_walk(Mod, FID0, Paths, State0, []) of + {ok, {FID, QIDs}, State} -> {ok, {FID, QIDs}, {Mod, State}}; + {error, Reason, State} -> {error, Reason, {Mod, State}} + end. + +do_walk(_Mod, FID, [], State, Acc) -> + {ok, {FID, lists:reverse(Acc)}, State}; +do_walk(Mod, #fid{qid = QID0, path = Path, state = FState0} = FID0, [P | Rest], State0, Acc) -> + case e9p:is_type(QID0, directory) of + true -> + case Mod:walk({QID0, FState0}, Path, P, State0) of + {false, State} when Acc =:= [] -> + % Per specification walk to first entry in name list must succeed + % (if any) otherwise return error. In subsequent steps we return + % successful list and last succeeded QID + {error, io_lib:format("Failed walk to ~p", [P]), State}; + {false, State} -> + {ok, {FID0, lists:reverse(Acc)}, State}; + {{QID, FState}, State} -> + FID = #fid{qid = QID, state = FState, path = Path ++ [P]}, + do_walk(Mod, FID, Rest, State, [QID | Acc]) + end; + false -> + {error, io_lib:format("Not directory ~p", [Path]), State0} + end. + +open({Mod, State0}, #fid{qid = QID, path = Path, state = FState0} = FID, Mode) -> + EMode = translate_mode(Mode), + case Mod:open({QID, FState0}, Path, EMode, State0) of + {ok, {FState, IOUnit}, State} -> + {ok, {FID#fid{state = FState}, IOUnit}, {Mod, State}}; + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} end. -open({Mod, State}, QID, Mode) -> - ?if_supported(Mod:open(QID, Mode, State)). +translate_mode(Mode) when Mode >= 16#10 -> + [trunc | translate_mode(Mode band 16#EF)]; +translate_mode(0) -> [read]; +translate_mode(1) -> [write]; +translate_mode(2) -> [append]; +translate_mode(3) -> [exec]. -create({Mod, State}, QID, Name, Perm, Mode) -> - ?if_supported(Mod:create(QID, Name, Perm, Mode, State)). +create({Mod, State}, #fid{qid = QID, path = Path, state = FState}, Name, Perm, Mode) -> + ?if_supported(Mod:create({QID, FState}, Path, Name, Perm, Mode, State)). -read({Mod, State}, QID, Offset, Length) -> - ?if_supported(Mod:read(QID, Offset, Length, State)). +read({Mod, State0}, #fid{qid = QID, path = Path, state = FState0} = FID, Offset, Length) -> + case Mod:read({QID, FState0}, Path, Offset, Length, State0) of + {ok, {FState, Data}, State} -> {ok, {FID#fid{state = FState}, Data}, {Mod, State}}; + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} + end. -write({Mod, State}, QID, Offset, Data) -> - ?if_supported(Mod:write(QID, Offset, Data, State)). +write({Mod, State0}, #fid{qid = QID, path = Path, state = FState0} = FID, Offset, Data) -> + case Mod:write({QID, FState0}, Path, Offset, Data, State0) of + {ok, {FState, Len}, State} -> {ok, {FID#fid{state = FState}, Len}, {Mod, State}}; + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} + end. -clunk({Mod, State0}, QID) -> +clunk({Mod, State0}, #fid{qid = QID, path = Path, state = FState}) -> case erlang:function_exported(Mod, clunk, 3) of true -> - maybe - {ok, State} ?= Mod:clunk(QID, State0), - {ok, {Mod, State}} - else - {error, Reason, StateE} -> - {error, Reason, {Mod, StateE}} + case Mod:clunk({QID, FState}, Path, State0) of + {ok, State} -> {ok, {Mod, State}}; + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} end; false -> {ok, {Mod, State0}} end. -remove({Mod, State}, QID) -> - ?if_supported(Mod:remove(QID, State)). +remove({Mod, State0}, #fid{qid = QID, path = Path, state = FState}) -> + case Mod:remove({QID, FState}, Path, State0) of + {ok, State} -> {ok, {Mod, State}}; + {error, Reason, State} -> {error, Reason, {Mod, State}} + end. -stat({Mod, State}, QID) -> - ?if_supported(Mod:stat(QID, State)). +stat({Mod, State0}, #fid{qid = QID, path = Path, state = FState0}) -> + case Mod:stat({QID, FState0}, Path, State0) of + {ok, Stat, State} -> {ok, Stat, {Mod, State}}; + {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} + end. -wstat({Mod, State}, QID, Stat) -> - ?if_supported(Mod:wstat(QID, Stat, State)). +wstat({Mod, State0}, #fid{qid = QID, path = Path, state = FState}, Stat) -> + case Mod:wstat({QID, FState}, Path, Stat, State0) of + {ok, State} -> {ok, {Mod, State}}; + {error, Reason, State} -> {error, Reason, {Mod, State}} + end. diff --git a/src/e9p_internal.hrl b/src/e9p_internal.hrl index fab19e5..639ed3c 100644 --- a/src/e9p_internal.hrl +++ b/src/e9p_internal.hrl @@ -53,6 +53,9 @@ -define(Twstat, 126). -define(Rwstat, 127). +-record(qid, {type, version, path}). +-record(fid, {qid, path, state}). + -record(tversion, {max_packet_size, version}). -record(rversion, {max_packet_size, version}). diff --git a/src/e9p_msg.erl b/src/e9p_msg.erl index 4047346..27cafc1 100644 --- a/src/e9p_msg.erl +++ b/src/e9p_msg.erl @@ -162,6 +162,10 @@ do_parse(?Tread, <>) -> {ok, #tread{fid = FID, offset = Offset, len = Len}}; do_parse(?Rread, <>) -> {ok, #rread{data = Data}}; +do_parse(?Twrite, <>) -> + {ok, #twrite{fid = FID, offset = Offset, data = Data}}; +do_parse(?Rwrite, <>) -> + {ok, #rwrite{len = Len}}; do_parse(Type, Data) -> {error, {invalid_message, Type, Data}}. @@ -273,7 +277,12 @@ do_encode(#tread{fid = FID, offset = Offset, len = Len}) -> {?Tread, <>}; do_encode(#rread{data = Data}) -> Len = iolist_size(Data), - {?Rread, [<> | Data]}. + {?Rread, [<> | Data]}; +do_encode(#twrite{fid = FID, offset = Offset, data = Data}) -> + Len = iolist_size(Data), + {?Twrite, [<>, Data]}; +do_encode(#rwrite{len = Len}) -> + {?Rwrite, [<>]}. encode_stat(Stat) -> #{ @@ -326,9 +335,9 @@ encode_str(Data0) -> [<>, Data]. binary_to_qid(<>) -> - #{type => Type, version => Version, path => Path, state => []}. + #qid{type = Type, version = Version, path = Path}. -qid_to_binary(#{type := Type, version := Version, path := Path}) -> +qid_to_binary(#qid{type = Type, version = Version, path = Path}) -> <>. time_to_encoded_sec(Sec) when is_integer(Sec) -> <>; diff --git a/src/e9p_server.erl b/src/e9p_server.erl index 4793b54..7b6f042 100644 --- a/src/e9p_server.erl +++ b/src/e9p_server.erl @@ -22,8 +22,6 @@ handler }). --record(fid, {qid, path, state}). - start_link(Port, Handler) -> proc_lib:start_link(?MODULE, setup_acceptor, [self(), Port, Handler]). @@ -60,7 +58,6 @@ accept_loop(LSock, Handler) -> loop(#state{socket = Sock} = State) -> case e9p_transport:read(Sock) of {ok, Tag, Data} -> - ?LOG_DEBUG(#{msg => Data}), try handle_message(Data, State#state.fids, State#state.handler) of {ok, Reply, FIDs, Handler} -> e9p_transport:send(Sock, Tag, Reply), @@ -92,7 +89,7 @@ handle_message(#tattach{fid = FID, uname = UName, aname = AName}, FIDs, Handler0 maybe {ok, QID, Handler} ?= e9p_fs:root(Handler0, UName, AName), NFIDs = FIDs#{FID => QID}, - {ok, #rattach{qid = QID}, NFIDs, Handler} + {ok, #rattach{qid = QID#fid.qid}, NFIDs, Handler} end; handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) -> @@ -106,13 +103,13 @@ handle_message(#topen{fid = FID, mode = Mode}, FIDs, Handler0) -> maybe {ok, QID} ?= get_qid(FIDs, FID), {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID, Mode), - {ok, #ropen{qid = QID, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} + {ok, #ropen{qid = QID#fid.qid, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} end; handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) -> maybe {ok, QID} ?= get_qid(FIDs, FID), {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:create(Handler0, QID, Name, Perm, Mode), - {ok, #rcreate{qid = NewQID, io_unit = IOUnit}, FIDs, Handler} + {ok, #rcreate{qid = NewQID#fid.qid, io_unit = IOUnit}, FIDs, Handler} end; handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> @@ -124,8 +121,8 @@ handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) -> maybe {ok, QID} ?= get_qid(FIDs, FID), - {ok, {NQID, Data}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), - {ok, #rread{data = Data}, FIDs#{FID => NQID}, Handler} + {ok, {NQID, Len}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), + {ok, #rwrite{len = Len}, FIDs#{FID => NQID}, Handler} end; handle_message(#tclunk{fid = FID}, FIDs, Handler0) -> diff --git a/src/e9p_unfs.erl b/src/e9p_unfs.erl index 7001491..44d97cb 100644 --- a/src/e9p_unfs.erl +++ b/src/e9p_unfs.erl @@ -9,28 +9,19 @@ -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/include/file.hrl"). --export([init/1, root/3, walk/3, stat/2, open/3, read/4, clunk/2]). - --doc """ -Create QID for given path. -""". -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. +-export([init/1, root/3, walk/4, stat/3, open/4, read/5, clunk/2, create/6, + write/5, remove/3, wstat/4]). -doc """ Create QID and Stat data for given path. """. -qid_stat(Root, Path) -> - case file:read_file_info(Path, [{time, posix}]) of +qid(Root, Path) -> + FullPath = filename:join([Root] ++ Path), + case file:read_file_info(FullPath, [{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}; + QID = e9p:make_qid(Type, 0, Inode), + Stat = file_info_to_stat(Path, QID, FI), + {ok, QID, Stat}; {error, _} = Error -> Error end. @@ -40,60 +31,84 @@ init(#{path := Path}) -> root(UName, AName, #{root := Root} = State) -> ?LOG_INFO(#{uname => UName, aname => AName}), maybe - {ok, Qid} ?= qid(Root), - {ok, Qid, State} + {ok, Qid, _Stat} ?= qid(Root, []), + {ok, {Qid, []}, State} end. -walk(#{state := {Root, _}}, ~"..", #{root := Root} = State) -> +walk(_QID, [], ~"..", State) -> {false, State}; -walk(#{state := {Path, _}}, ~"..", State) -> - Next = filename:dirname(Path), - case qid(Next) of - {ok, NQid} -> {NQid, State}; +walk(_QID, Path, ~"..", #{root := Root} = State) -> + case qid(Root, lists:droplast(Path)) of + {ok, NQid, _Stat} -> {{NQid, []}, State}; {error, _} -> {false, State} end; -walk(#{state := {Path, _}}, File, State) -> - Next = filename:join(Path, File), - case qid(Next) of - {ok, NQid} -> {NQid, State}; +walk(_QID, Path, File, #{root := Root} = State) -> + case qid(Root, Path ++ [File]) of + {ok, NQid, _Stat} -> {{NQid, []}, State}; {error, _} -> {false, State} end. -stat(#{state := {Path, _}} = QID, #{root := Root} = State) -> - case file:read_file_info(Path, [{time, posix}]) of +stat({QID, _}, Path, #{root := Root} = State) -> + FullPath = filename:join([Root] ++ Path), + case file:read_file_info(FullPath, [{time, posix}]) of {ok, FileInfo} -> - Stat = file_info_to_stat(Root, QID, FileInfo), + Stat = file_info_to_stat(Path, QID, FileInfo), {ok, Stat, State}; {error, Error} -> {error, Error, State} end. -open(#{state := {Path, []}} = QID, _Mode, State) -> +open({QID, []}, Path, Mode, #{root := Root} = State) -> + FullPath = filename:join([Root] ++ Path), QS = case e9p:is_type(QID, directory) of true -> - {ok, List} = file:list_dir(Path), + {ok, List} = file:list_dir(FullPath), {dir, List}; false -> - {ok, FD} = file:open(Path, [raw, read, binary]), + {Trunc, Opts} = translate_mode(Mode), + {ok, FD} = file:open(FullPath, [raw, binary | Opts]), + if Trunc -> file:truncate(FD); true -> ok end, {regular, FD} end, - NQID = QID#{state => {Path, QS}}, - {ok, {NQID, 0}, State}. + {ok, {QS, 0}, State}. + +create(_QID, _Path, _Name, _Perm, _Mode, State) -> + {error, "Unsupported", State}. + +remove({QID, _} = FID, Path, #{root := Root} = State0) -> + FullPath = filename:join([Root] ++ Path), + {ok, State} = clunk(FID, State0), + case case e9p:is_type(QID, directory) of + true -> file:del_dir(FullPath); + false -> file:delete(FullPath) + end of + ok -> {ok, State}; + {error, Reason} -> + {error, io_lib:format("Failed to remove path: ~p", [Reason]), State} + end. -read(#{state := {_, {regular, FD}}} = QID, Offset, Len, State) -> +translate_mode([trunc | Rest]) -> + {_, Mode} = translate_mode(Rest), + {true, Mode}; +translate_mode([read]) -> {false, [read]}; +translate_mode([write]) -> {false, [read, write]}; +translate_mode([append]) -> {false, [read, write]}; +translate_mode([exec]) -> {false, [read]}. + +read({_QID, {regular, FD}}, _Path, Offset, Len, State) -> case file:pread(FD, Offset, Len) of - {ok, Data} -> {ok, {QID, Data}, State}; - eof -> {ok, {QID, []}, State}; + {ok, Data} -> {ok, {{regular, FD}, Data}, State}; + eof -> {ok, {{regular, FD}, []}, State}; {error, Err} -> {error, Err, State} end; -read(#{state := {Path, {dir, List}}} = QID, _Offset, Len, #{root := Root} = State) -> +read({_QID, {dir, List}}, Path, _Offset, Len, #{root := Root} = State) -> {Remaining, Data} = readdir(Root, Path, List, Len, []), - {ok, {QID#{state => {Path, {dir, Remaining}}}, Data}, State}. + {ok, {{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)), + {ok, _QID, Stat} = qid(Root, Path ++ [Next]), Encoded = e9p_msg:encode_stat(Stat), Size = iolist_size(Encoded), if @@ -101,15 +116,32 @@ readdir(Root, Path, [Next | Rest], Len, Acc) -> true -> readdir(Root, Path, Rest, Len - Size, [Encoded | Acc]) end. -clunk(#{state := {_Path, {refular, FD}}}, State) -> +write({_QID, {regular, FD}}, _Path, Offset, Data, State) -> + case file:pwrite(FD, Offset, Data) of + ok -> {ok, {{regular, FD}, iolist_size(Data)}, State}; + {error, Err} -> {error, io_lib:format("Write error ~p", [Err]), State} + end. + +clunk({_, {regular, FD}}, State) -> ok = file:close(FD), {ok, State}; clunk(_QID, State) -> {ok, State}. +wstat(_QID, Path, Stat, #{root := Root} = State) -> + FileInfo = stat_to_file_info(Stat), + FullPath = filename:join([Root] ++ Path), + + case file:write_file_info(FullPath, FileInfo, [{time, posix}]) of + ok -> {ok, State}; + {error, Reason} -> + {error, io_lib:format("Couldn't write file stat: ~p", [Reason]), + State} + end. + file_info_to_stat( - Root, - #{state := {Path, _}} = QID, + Path, + QID, #file_info{ size = Len, atime = Atime, @@ -117,8 +149,8 @@ file_info_to_stat( mode = Mode }) -> Name = if - Root == Path -> ~"/"; - true -> filename:basename(Path) + Path == [] -> ~"/"; + true -> lists:last(Path) end, #{ qid => QID, @@ -128,3 +160,15 @@ file_info_to_stat( length => Len, name => Name }. + +stat_to_file_info(Stat) -> + #{ + mode := Mode, + atime := Atime, + mtime := Mtime + } = Stat, + #file_info{ + mode = Mode, + atime = Atime, + mtime = Mtime + }. -- 2.51.2