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 + }.