diff --git a/src/e9p_err.erl b/src/e9p_err.erl new file mode 100644 index 0000000..0f10d4d --- /dev/null +++ b/src/e9p_err.erl @@ -0,0 +1,158 @@ +-module(e9p_err). + +-export([normalize/1, error/1]). + +-define(eundef, 16#FFFFFFFF). + +-include("e9p_internal.hrl"). + +error(Atom) when is_atom(Atom) -> + {Errno, Msg} = normalize(Atom), + #rerror{msg = Msg, errno = Errno}; +error({Msg, Atom}) when is_atom(Atom) -> + {Errno, _Msg} = normalize(Atom), + #rerror{msg = Msg, errno = Errno}; +error(Msg) -> + #rerror{msg = Msg, errno = ?eundef}. + +-spec normalize(file:posix() | eundef) -> {non_neg_integer(), binary()}. +normalize(eperm) -> {1, ~"Operation not permitted"}; +normalize(enoent) -> {2, ~"No such file or directory"}; +normalize(esrch) -> {3, ~"No such process"}; +normalize(eintr) -> {4, ~"Interrupted system call"}; +normalize(eio) -> {5, ~"Input/output error"}; +normalize(enxio) -> {6, ~"No such device or address"}; +% normalize(e2big) -> {7, ~"Argument list too long"}; +% normalize(enoexec) -> {8, ~"Exec format error"}; +normalize(ebadf) -> {9, ~"Bad file descriptor"}; +% normalize(echild) -> {10, ~"No child processes"}; +normalize(eagain) -> {11, ~"Resource temporarily unavailable"}; +normalize(enomem) -> {12, ~"Cannot allocate memory"}; +normalize(eacces) -> {13, ~"Permission denied"}; +normalize(efault) -> {14, ~"Bad address"}; +normalize(enotblk) -> {15, ~"Block device required"}; +normalize(ebusy) -> {16, ~"Device or resource busy"}; +normalize(eexist) -> {17, ~"File exists"}; +normalize(exdev) -> {18, ~"Invalid cross-device link"}; +normalize(enodev) -> {19, ~"No such device"}; +normalize(enotdir) -> {20, ~"Not a directory"}; +normalize(eisdir) -> {21, ~"Is a directory"}; +normalize(einval) -> {22, ~"Invalid argument"}; +normalize(enfile) -> {23, ~"Too many open files in system"}; +normalize(emfile) -> {24, ~"Too many open files"}; +% normalize(enotty) -> {25, ~"Inappropriate ioctl for device"}; +normalize(etxtbsy) -> {26, ~"Text file busy"}; +normalize(efbig) -> {27, ~"File too large"}; +normalize(enospc) -> {28, ~"No space left on device"}; +normalize(espipe) -> {29, ~"Illegal seek"}; +normalize(erofs) -> {30, ~"Read-only file system"}; +normalize(emlink) -> {31, ~"Too many links"}; +normalize(epipe) -> {32, ~"Broken pipe"}; +% normalize(edom) -> {33, ~"Numerical argument out of domain"}; +normalize(erange) -> {34, ~"Numerical result out of range"}; +normalize(edeadlk) -> {35, ~"Resource deadlock avoided"}; +normalize(enametoolong) -> {36, ~"File name too long"}; +normalize(enolck) -> {37, ~"No locks available"}; +normalize(enosys) -> {38, ~"Function not implemented"}; +% normalize(enotempty) -> {39, ~"Directory not empty"}; +normalize(eloop) -> {40, ~"Too many levels of symbolic links"}; +% normalize(enomsg) -> {42, ~"No message of desired type"}; +% normalize(eidrm) -> {43, ~"Identifier removed"}; +% normalize(echrng) -> {44, ~"Channel number out of range"}; +% normalize(el2nsync) -> {45, ~"Level 2 not synchronized"}; +% normalize(el3hlt) -> {46, ~"Level 3 halted"}; +% normalize(el3rst) -> {47, ~"Level 3 reset"}; +% normalize(elnrng) -> {48, ~"Link number out of range"}; +% normalize(eunatch) -> {49, ~"Protocol driver not attached"}; +% normalize(enocsi) -> {50, ~"No CSI structure available"}; +% normalize(el2hlt) -> {51, ~"Level 2 halted"}; +% normalize(ebade) -> {52, ~"Invalid exchange"}; +% normalize(ebadr) -> {53, ~"Invalid request descriptor"}; +% normalize(exfull) -> {54, ~"Exchange full"}; +% normalize(enoano) -> {55, ~"No anode"}; +% normalize(ebadrqc) -> {56, ~"Invalid request code"}; +% normalize(ebadslt) -> {57, ~"Invalid slot"}; +% normalize(ebfont) -> {59, ~"Bad font file format"}; +normalize(enostr) -> {60, ~"Device not a stream"}; +% normalize(enodata) -> {61, ~"No data available"}; +% normalize(etime) -> {62, ~"Timer expired"}; +normalize(enosr) -> {63, ~"Out of streams resources"}; +% normalize(enonet) -> {64, ~"Machine is not on the network"}; +% normalize(enopkg) -> {65, ~"Package not installed"}; +% normalize(eremote) -> {66, ~"Object is remote"}; +normalize(enolink) -> {67, ~"Link has been severed"}; +% normalize(eadv) -> {68, ~"Advertise error"}; +% normalize(esrmnt) -> {69, ~"Srmount error"}; +% normalize(ecomm) -> {70, ~"Communication error on send"}; +% normalize(eproto) -> {71, ~"Protocol error"}; +normalize(emultihop) -> {72, ~"Multihop attempted"}; +% normalize(edotdot) -> {73, ~"RFS specific error"}; +normalize(ebadmsg) -> {74, ~"Bad message"}; +normalize(eoverflow) -> {75, ~"Value too large for defined data type"}; +% normalize(enotuniq) -> {76, ~"Name not unique on network"}; +% normalize(ebadfd) -> {77, ~"File descriptor in bad state"}; +% normalize(eremchg) -> {78, ~"Remote address changed"}; +% normalize(elibacc) -> {79, ~"Can not access a needed shared library"}; +% normalize(elibbad) -> {80, ~"Accessing a corrupted shared library"}; +% normalize(elibscn) -> {81, ~".lib section in a.out corrupted"}; +% normalize(elibmax) -> {82, ~"Attempting to link in too many shared libraries"}; +% normalize(elibexec) -> {83, ~"Cannot exec a shared library directly"}; +% normalize(eilseq) -> {84, ~"Invalid or incomplete multibyte or wide character"}; +% normalize(erestart) -> {85, ~"Interrupted system call should be restarted"}; +% normalize(estrpipe) -> {86, ~"Streams pipe error"}; +% normalize(eusers) -> {87, ~"Too many users"}; +% normalize(enotsock) -> {88, ~"Socket operation on non-socket"}; +% normalize(edestaddrreq) -> {89, ~"Destination address required"}; +% normalize(emsgsize) -> {90, ~"Message too long"}; +% normalize(eprototype) -> {91, ~"Protocol wrong type for socket"}; +% normalize(enoprotoopt) -> {92, ~"Protocol not available"}; +% normalize(eprotonosupport) -> {93, ~"Protocol not supported"}; +% normalize(esocktnosupport) -> {94, ~"Socket type not supported"}; +normalize(eopnotsupp) -> {95, ~"Operation not supported"}; +% normalize(epfnosupport) -> {96, ~"Protocol family not supported"}; +% normalize(eafnosupport) -> {97, ~"Address family not supported by protocol"}; +% normalize(eaddrinuse) -> {98, ~"Address already in use"}; +% normalize(eaddrnotavail) -> {99, ~"Cannot assign requested address"}; +% normalize(enetdown) -> {100, ~"Network is down"}; +% normalize(enetunreach) -> {101, ~"Network is unreachable"}; +% normalize(enetreset) -> {102, ~"Network dropped connection on reset"}; +% normalize(econnaborted) -> {103, ~"Software caused connection abort"}; +% normalize(econnreset) -> {104, ~"Connection reset by peer"}; +normalize(enobufs) -> {105, ~"No buffer space available"}; +% normalize(eisconn) -> {106, ~"Transport endpoint is already connected"}; +% normalize(enotconn) -> {107, ~"Transport endpoint is not connected"}; +% normalize(eshutdown) -> {108, ~"Cannot send after transport endpoint shutdown"}; +% normalize(etoomanyrefs) -> {109, ~"Too many references: cannot splice"}; +% normalize(etimedout) -> {110, ~"Connection timed out"}; +% normalize(econnrefused) -> {111, ~"Connection refused"}; +% normalize(ehostdown) -> {112, ~"Host is down"}; +% normalize(ehostunreach) -> {113, ~"No route to host"}; +% normalize(ealready) -> {114, ~"Operation already in progress"}; +% normalize(einprogress) -> {115, ~"Operation now in progress"}; +normalize(estale) -> {116, ~"Stale file handle"}; +% normalize(euclean) -> {117, ~"Structure needs cleaning"}; +% normalize(enotnam) -> {118, ~"Not a Xenix named type file"}; +% normalize(enavail) -> {119, ~"No Xenix semaphores available"}; +% normalize(eisnam) -> {120, ~"Is a named type file"}; +% normalize(eremoteio) -> {121, ~"Remote I/O error"}; +normalize(edquot) -> {122, ~"Disk quota exceeded"}; +% normalize(enomedium) -> {123, ~"No medium found"}; +% normalize(emediumtype) -> {124, ~"Wrong medium type"}; +% normalize(ecanceled) -> {125, ~"Operation canceled"}; +% normalize(enokey) -> {126, ~"Required key not available"}; +% normalize(ekeyexpired) -> {127, ~"Key has expired"}; +% normalize(ekeyrevoked) -> {128, ~"Key has been revoked"}; +% normalize(ekeyrejected) -> {129, ~"Key was rejected by service"}; +% normalize(eownerdead) -> {130, ~"Owner died"}; +% normalize(enotrecoverable) -> {131, ~"State not recoverable"}; +% normalize(erfkill) -> {132, ~"Operation not possible due to RF-kill"}; +% normalize(ehwpoison) -> {133, ~"Memory page has hardware error"}; +normalize(enotsup) -> {134, ~"Not supported parameter or option"}; +% normalize(enomedium) -> {135, ~"Missing media"}; +% normalize(eilseq) -> {138, ~"Invalid multibyte sequence"}; +% normalize(eoverflow) -> {139, ~"Value too large"}; % Duplicated +% normalize(ecanceled) -> {140, ~"Asynchronous operation stopped before normal completion"}; +% normalize(enotrecoverable) -> {141, ~"State not recoverable"}; +% normalize(eownerdead) -> {142, ~"Previous owner died"}; +% normalize(estrpipe) -> {143, ~"Streams pipe error"}; +normalize(_Other) -> {?eundef, ~"Unknown error"}. diff --git a/src/e9p_fs.erl b/src/e9p_fs.erl index d541f4f..725125a 100644 --- a/src/e9p_fs.erl +++ b/src/e9p_fs.erl @@ -7,9 +7,10 @@ -module(e9p_fs). -export([ - init/1, - root/3, - walk/3, + init/2, + negotiate/3, + root/5, + walk/4, open/3, create/5, read/4, @@ -23,7 +24,10 @@ -include("e9p_internal.hrl"). -include_lib("kernel/include/logger.hrl"). --export_type([state/0, fid/0, path/0, result/0, result/1]). +-define(eundef, 16#FFFFFFFF). + +-type errno() :: file:posix() | eundef. +-type err() :: errno() | unicode:chardata() | {unicode:chardata(), errno()}. -type state() :: term(). -type fid() :: {QID :: e9p:qid(), State :: fid_state()}. @@ -32,20 +36,6 @@ -type result() :: {ok, state()} | {error, term(), state()}. -type result(T) :: {ok, T, state()} | {error, term(), state()}. --define(if_supported(Code), - case erlang:function_exported(Mod, ?FUNCTION_NAME, ?FUNCTION_ARITY) of - true -> - case (fun() -> Code end)() of - {ok, Ret, NewState} -> - {ok, Ret, {Mod, NewState}}; - {error, Error, NewState} -> - {error, Error, {Mod, NewState}} - end; - false -> - {error, nosupport, {Mod, State}} - end -). - %% Setup state for given filesystem. -callback init(term()) -> {ok, state()} @@ -56,19 +46,27 @@ %% 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()) -> +-callback root(UName :: unicode:chardata(), AName :: unicode:chardata(), UID :: e9p:u32(), state()) -> {ok, fid_state(), state()}. --callback flush(state()) -> result(). - %% Walk through the given path starting at the `QID' --callback walk(fid(), File :: unicode:chardata(), unicode:chardata(), state()) -> +-callback walk(e9p:qid(), fid_state(), File :: unicode:chardata(), unicode:chardata(), state()) -> {fid() | false, state()}. --callback open(fid(), path(), Mode :: integer(), state()) -> result({fid_state(), e9p:u32()}). +-callback open(e9p:qid(), fid_state(), path(), Mode :: integer(), state()) -> + result({fid_state(), e9p:u32()}). + +% -callback stat(e9p:qid() | walk, fid_state() | undefined, path(), state()) -> +% {ok, {e9p:stat(), fid_state()}, state()} | {error, term(), state()}. +% -callback dir_list(e9p:qid(), fid_state(), path(), state()) -> +% {ok, {list, [e9p:filename()]} | {stream, term()}, state()} +% | {error, term(), state()}. +% -callback dir_next(e9p:qid(), fid_state(), path(), term(), state()) -> +% {ok, e9p:filename() | false, state()} | {error, term(), state()}. -callback create( - fid(), + e9p:qid(), + fid_state(), path(), Name :: unicode:chardata(), Perm :: e9p:u32(), @@ -78,7 +76,8 @@ %% Read data from file indicated by `QID' -callback read( - fid(), + e9p:qid(), + fid_state(), path(), Offset :: non_neg_integer(), Length :: non_neg_integer(), @@ -87,62 +86,101 @@ %% Write data to file indicated by `QID' -callback write( - fid(), + e9p:qid(), + fid_state(), path(), Offset :: non_neg_integer(), Data :: iodata(), state() ) -> result({fid_state(), non_neg_integer()}). --callback clunk(fid(), path(), state()) -> result(). +-callback clunk(e9p:qid(), fid_state(), path(), state()) -> result(). --callback remove(fid(), path(), state()) -> result(). +-callback remove(e9p:qid(), fid_state(), path(), state()) -> result(). %% Return stat data for file indicated by `QID' --callback stat(fid(), path(), state()) -> result(map()). +-callback stat(e9p:qid(), fid_state(), path(), state()) -> result(map()). %% Write stat data for file indicated by `QID' --callback wstat(fid(), path(), map(), state()) -> result(). +-callback wstat(e9p:qid(), fid_state(), path(), map(), state()) -> result(). + +-optional_callbacks([clunk/4]). + +-record(state, { + mod, + state, + version, + fids = #{}, + max_packet_size +}). + +get_qid(#state{fids = FIDs} = State, FID) -> + case FIDs of + #{FID := QID} -> {ok, QID}; + _ -> {error, ~"Unknown FID", State} + end. +put_qid(#state{fids = FIDs} = State, FID, #fid{} = Data) -> + State#state{fids = FIDs#{FID => Data}}. --optional_callbacks([ - flush/1, - clunk/3 -]). +remove_qid(#state{fids = FIDs} = State, FID) -> + State#state{fids = maps:remove(FID, FIDs)}. -init({Mod, State}) -> - case Mod:init(State) of - {ok, NewState} -> {ok, {Mod, NewState}}; - Error -> Error +init(Mod, State) -> + maybe + {ok, NewState} ?= Mod:init(State), + {ok, #state{mod = Mod, state = NewState}} end. -root({Mod, State}, UName, AName) -> - case Mod:root(UName, AName, State) of - {ok, {QID, FState}, NewState} -> - {ok, #fid{qid = QID, path = [], state = FState}, {Mod, NewState}} +negotiate(#state{} = State, ~"9P2000", MPS) -> + {ok, State#state{version = legacy, max_packet_size = MPS}}; +negotiate(#state{} = State, ~"9P2000.u", MPS) -> + {ok, State#state{version = unix, max_packet_size = MPS}}; +negotiate(#state{} = State, _Other, _MPS) -> + {error, ~"Unsupported version", State}. + +root(#state{mod = Mod} = State, FID, UName, AName, UID) -> + case Mod:root(UName, AName, UID, State#state.state) of + {ok, {QID, FState}, NewHState} -> + NewState = put_qid(State, FID, #fid{qid = QID, path = [], state = FState}), + {ok, QID, NewState#state{state = NewHState}} end. -doc """ Walk through paths starting at QID. """. -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}} +walk(State, CurrFID, NewFID, []) -> + maybe + {ok, QID} ?= get_qid(State, CurrFID), + {ok, [], put_qid(State, NewFID, QID)} + end; +walk(#state{mod = Mod, state = MState} = State, CurrFID, NewFID, Paths) -> + maybe + {ok, Curr} ?= get_qid(State, CurrFID), + case do_walk(Mod, Curr, Paths, MState, []) of + {ok, [], NewMState} -> + {error, enoent, State#state{state = NewMState}}; + {ok, {FID, QIDs}, NewMState} -> + NewState = put_qid(State, NewFID, FID), + {ok, QIDs, NewState#state{state = NewMState}}; + {error, Reason, State} -> + {error, Reason, {Mod, State}} + end end. do_walk(_Mod, FID, [], State, Acc) -> {ok, {FID, lists:reverse(Acc)}, State}; do_walk(_Mod, #fid{path = []}, [~".." | _Names], State, _Acc) -> - {error, "Cannot walt to root parent of root directory", State}; + % Trying to walk outside of root - return root + {false, 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 + 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}; + {error, enoent, State}; {false, State} -> {ok, {FID0, lists:reverse(Acc)}, State}; {{QID, FState}, State} -> @@ -150,16 +188,19 @@ do_walk(Mod, #fid{qid = QID0, path = Path, state = FState0} = FID0, [P | Rest], do_walk(Mod, FID, Rest, State, [QID | Acc]) end; false -> - {error, io_lib:format("Not directory ~p", [Path]), State0} + {error, enotdir, State0} end. -open({Mod, State0}, #fid{qid = QID, path = Path, state = FState0} = FID, Mode) -> +open(#state{mod = Mod, state = MState0} = State, FID, Mode) -> + {ok, #fid{qid = QID, path = Path, state = FState0} = Data} = get_qid(State, FID), 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}}; + case Mod:open(QID, FState0, Path, EMode, MState0) of + {ok, {FState, IOUnit}, NewMState} -> + NewFID = Data#fid{state = FState}, + NewState = put_qid(State, FID, NewFID), + {ok, {QID, IOUnit}, NewState#state{state = NewMState}}; {error, Reason, StateE} -> - {error, Reason, {Mod, StateE}} + {error, Reason, State#state{state = StateE}} end. translate_mode(Mode) when Mode >= 16#10 -> @@ -173,46 +214,73 @@ translate_mode(2) -> translate_mode(3) -> [exec]. -create({Mod, State}, #fid{qid = QID, path = Path, state = FState}, Name, Perm, Mode) -> - ?if_supported(Mod:create({QID, FState}, Path, Name, Perm, Mode, State)). +create(#state{mod = Mod, state = MState} = State, FID, Name, Perm, Mode) -> + #fid{qid = QID, path = Path, state = FState} = get_qid(State, FID), + case Mod:create({QID, FState}, Path, Name, Perm, Mode, MState) of + {ok, {{NewQID, NewFState}, IOUnit}, NewMState} -> + NewFID = #fid{qid = NewQID, state = NewFState, path = Path ++ [Name]}, + NewState = put_qid(State, FID, NewFID), + {ok, {NewQID, IOUnit}, NewState#state{state = NewMState}}; + {error, Reason, StateE} -> + {error, Reason, State#state{state = StateE}} + end. -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}} +read(#state{mod = Mod, state = MState} = State, FID, Offset, Length) -> + {ok, #fid{qid = QID, path = Path, state = FState0} = File} = get_qid(State, FID), + case Mod:read(QID, FState0, Path, Offset, Length, MState) of + {ok, {FState, Data}, NewMState} -> + NewState = put_qid( + State#state{state = NewMState}, + FID, + File#fid{state = FState} + ), + {ok, Data, NewState}; + {error, Reason, StateE} -> + {error, Reason, State#state{state = StateE}} end. -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}} +write( + #state{mod = Mod, state = MState} = State, + #fid{qid = QID, path = Path, state = FState0} = FID, + Offset, + Data +) -> + case Mod:write(QID, FState0, Path, Offset, Data, MState) of + {ok, {FState, Len}, NewMState} -> + {ok, {FID#fid{state = FState}, Len}, State#state{state = NewMState}}; + {error, Reason, StateE} -> + {error, Reason, State#state{state = StateE}} end. -clunk({Mod, State0}, #fid{qid = QID, path = Path, state = FState}) -> - case erlang:function_exported(Mod, clunk, 3) of +clunk(#state{mod = Mod, state = MState} = State, FID) -> + case erlang:function_exported(Mod, clunk, 4) of true -> - case Mod:clunk({QID, FState}, Path, State0) of - {ok, State} -> {ok, {Mod, State}}; - {error, Reason, StateE} -> {error, Reason, {Mod, StateE}} + #fid{qid = QID, path = Path, state = FState} = get_qid(State, FID), + case Mod:clunk(QID, FState, Path, MState) of + {ok, NewMState} -> {ok, remove_qid(State#state{state = NewMState}, FID)}; + {error, Reason, StateE} -> {error, Reason, State#state{state = StateE}} end; false -> - {ok, {Mod, State0}} + {ok, remove_qid(State, FID)} end. -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}} +remove(#state{mod = Mod, state = MState} = State, #fid{qid = QID, path = Path, state = FState}) -> + case Mod:remove(QID, FState, Path, MState) of + {ok, NewMState} -> {ok, State#state{state = NewMState}}; + {error, Reason, StateE} -> {error, Reason, State#state{state = StateE}} end. -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}} +stat(#state{mod = Mod, state = MState} = State, FID) -> + {ok, #fid{qid = QID, path = Path, state = FState0}} = get_qid(State, FID), + case Mod:stat(QID, FState0, Path, MState) of + {ok, Stat, NewMState} -> {ok, Stat, State#state{state = NewMState}}; + {error, Reason, StateE} -> {error, Reason, State#state{state = StateE}} end. -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}} +wstat( + #state{mod = Mod, state = MState} = State, #fid{qid = QID, path = Path, state = FState}, Stat +) -> + case Mod:wstat(QID, FState, Path, Stat, MState) of + {ok, NewMState} -> {ok, State#state{state = NewMState}}; + {error, Reason, StateE} -> {error, Reason, State#state{state = StateE}} end. diff --git a/src/e9p_server.erl b/src/e9p_server.erl index 649c3f3..4c0f436 100644 --- a/src/e9p_server.erl +++ b/src/e9p_server.erl @@ -22,7 +22,6 @@ -record(state, { socket, % trans_mod = gen_tcp, - fids = #{}, handler }). @@ -32,12 +31,12 @@ start(Port, Handler) -> start_link(Port, Handler) -> proc_lib:start_link(?MODULE, setup_acceptor, [self(), Port, Handler]). -setup_acceptor(Parent, Port, Handler0) -> +setup_acceptor(Parent, Port, {HandlerMod, HandlerState0}) -> maybe {ok, LSock} ?= gen_tcp:listen(Port, [{ip, {0, 0, 0, 0}}, binary, {active, false}]), - {ok, Handler} ?= e9p_fs:init(Handler0), + {ok, HandlerState} ?= e9p_fs:init(HandlerMod, HandlerState0), proc_lib:init_ack(Parent, {ok, self()}), - ?MODULE:accept_loop(LSock, Handler) + ?MODULE:accept_loop(LSock, HandlerState) else {error, _} = Error -> proc_lib:init_fail(Parent, Error) @@ -63,10 +62,10 @@ accept_loop(LSock, Handler) -> loop(#state{socket = Sock} = State) -> case e9p_transport:read(Sock) of {ok, Tag, Data} -> - try handle_message(Data, State#state.fids, State#state.handler) of - {ok, Reply, FIDs, Handler} -> + try handle_message(Data, State) of + {ok, Reply, NewState} -> e9p_transport:send(Sock, Tag, Reply), - ?MODULE:loop(State#state{fids = FIDs, handler = Handler}); + ?MODULE:loop(NewState); {error, Err, RHandler} -> e9p_transport:send(Sock, Tag, #rerror{msg = Err}), ?MODULE:loop(State#state{handler = RHandler}) @@ -86,83 +85,82 @@ loop(#state{socket = Sock} = State) -> ok end. -handle_message(#tversion{version = ~"9P2000.u", max_packet_size = MPS}, FIDs, Handler) -> - {ok, #rversion{version = ~"9P2000.u", max_packet_size = MPS}, FIDs, Handler}; -handle_message(#tversion{version = <<"9P2000", _/binary>>, max_packet_size = MPS}, FIDs, Handler) -> - % Currently only "basic" 9p2000 version is supported, without any extensions - % like `.u` or `.L` - {ok, #rversion{version = ~"9P2000", max_packet_size = MPS}, FIDs, Handler}; -handle_message(#tflush{}, FIDs, Handler) -> +do_call(#state{handler = Handler} = State, Func, Args) -> + case apply(e9p_fs, Func, [Handler | Args]) of + {ok, Result, NewHandler} -> + {ok, Result, State#state{handler = NewHandler}}; + {error, Reason, NewHandler} -> + {error, Reason, State#state{handler = NewHandler}} + end. + +do_cast(#state{handler = Handler} = State, Func, Args) -> + case apply(e9p_fs, Func, [Handler | Args]) of + {ok, NewHandler} -> + {ok, State#state{handler = NewHandler}}; + {error, Reason, NewHandler} -> + {error, Reason, State#state{handler = NewHandler}} + end. + +handle_message(#tversion{version = Version, max_packet_size = MPS}, State) -> + maybe + {ok, NewState} ?= do_cast(State, negotiate, [Version, MPS]), + {ok, #rversion{version = Version, max_packet_size = MPS}, NewState} + end; +handle_message(#tflush{}, State) -> % Currently there is no support for parallel messages, so this does simply % nothing - {ok, #rflush{}, FIDs, Handler}; -handle_message(#tattach{fid = FID, uname = UName, aname = AName, uid = UID}, FIDs, Handler0) -> + {ok, #rflush{}, State}; +handle_message(#tattach{fid = FID, uname = UName, aname = AName, uid = UID}, State) -> ?LOG_NOTICE(#{uname => UName, aname => AName, uid => UID}), maybe - {ok, QID, Handler} ?= e9p_fs:root(Handler0, UName, AName), - NFIDs = FIDs#{FID => QID}, - {ok, #rattach{qid = QID#fid.qid}, NFIDs, Handler} + {ok, QID, NewState} ?= do_call(State, root, [FID, UName, AName, UID]), + {ok, #rattach{qid = QID}, NewState} end; -handle_message(#twalk{fid = FID, new_fid = NewFID, names = Paths}, FIDs, Handler0) -> +handle_message(#twalk{fid = FID, new_fid = NewFID, names = Names}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, {NewQID, QIDs}, Handler} ?= e9p_fs:walk(Handler0, QID, Paths), - {ok, #rwalk{qids = QIDs}, FIDs#{NewFID => NewQID}, Handler} + {ok, QIDs, NewState} ?= do_call(State, walk, [FID, NewFID, Names]), + {ok, #rwalk{qids = QIDs}, NewState} end; -handle_message(#topen{fid = FID, mode = Mode}, FIDs, Handler0) -> +handle_message(#topen{fid = FID, mode = Mode}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:open(Handler0, QID, Mode), - {ok, #ropen{qid = QID#fid.qid, io_unit = IOUnit}, FIDs#{FID => NewQID}, Handler} + {ok, {QID, IOUnit}, NewState} ?= do_call(State, open, [FID, Mode]), + {ok, #ropen{qid = QID, io_unit = IOUnit}, NewState} end; -handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, FIDs, Handler0) -> +handle_message(#tcreate{fid = FID, name = Name, perm = Perm, mode = Mode}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, {NewQID, IOUnit}, Handler} ?= e9p_fs:create(Handler0, QID, Name, Perm, Mode), - {ok, #rcreate{qid = NewQID#fid.qid, io_unit = IOUnit}, FIDs, Handler} + {ok, {NewQID, IOUnit}, NewState} ?= do_call(State, create, [FID, Name, Perm, Mode]), + {ok, #rcreate{qid = NewQID, io_unit = IOUnit}, NewState} end; -handle_message(#tread{fid = FID, offset = Offset, len = Len}, FIDs, Handler0) -> +handle_message(#tread{fid = FID, offset = Offset, len = Len}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, {NQID, Data}, Handler} ?= e9p_fs:read(Handler0, QID, Offset, Len), - {ok, #rread{data = Data}, FIDs#{FID => NQID}, Handler} + {ok, Data, NewState} ?= do_call(State, read, [FID, Offset, Len]), + {ok, #rread{data = Data}, NewState} end; -handle_message(#twrite{fid = FID, offset = Offset, data = Data}, FIDs, Handler0) -> +handle_message(#twrite{fid = FID, offset = Offset, data = Data}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, {NQID, Len}, Handler} ?= e9p_fs:write(Handler0, QID, Offset, Data), - {ok, #rwrite{len = Len}, FIDs#{FID => NQID}, Handler} + {ok, Len, NewState} ?= do_call(State, write, [FID, Offset, Data]), + {ok, #rwrite{len = Len}, NewState} end; -handle_message(#tclunk{fid = FID}, FIDs, Handler0) -> +handle_message(#tclunk{fid = FID}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, Handler} ?= e9p_fs:clunk(Handler0, QID), - NFIDs = maps:remove(FID, FIDs), - {ok, #rclunk{}, NFIDs, Handler} + {ok, NewState} ?= do_cast(State, clunk, [FID]), + {ok, #rclunk{}, NewState} end; -handle_message(#tremove{fid = FID}, FIDs, Handler0) -> +handle_message(#tremove{fid = FID}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, Handler} ?= e9p_fs:remove(Handler0, QID), - {ok, #rremove{}, FIDs, Handler} + {ok, NewState} ?= do_cast(State, remove, [FID]), + {ok, #rremove{}, NewState} end; -handle_message(#tstat{fid = FID}, FIDs, Handler0) -> +handle_message(#tstat{fid = FID}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, Stat, Handler} ?= e9p_fs:stat(Handler0, QID), - {ok, #rstat{stat = Stat}, FIDs, Handler} + {ok, Stat, NewState} ?= do_call(State, stat, [FID]), + {ok, #rstat{stat = Stat}, NewState} end; -handle_message(#twstat{fid = FID, stat = Stat}, FIDs, Handler0) -> +handle_message(#twstat{fid = FID, stat = Stat}, State) -> maybe - {ok, QID} ?= get_qid(FIDs, FID), - {ok, Handler} ?= e9p_fs:wstat(Handler0, QID, Stat), - {ok, #rwstat{}, FIDs, Handler} + {ok, NewState} ?= do_call(State, wstat, [FID, Stat]), + {ok, #rwstat{}, NewState} end; -handle_message(_Msg, _FIDs, Handler) -> - {error, ~"Unknown request type", Handler}. -get_qid(FIDs, FID) -> - case FIDs of - #{FID := QID} -> {ok, QID}; - _ -> {error, io_lib:fwrite(~"Unknown FID: ~B", [FID])} - end. +handle_message(_Msg, State) -> + {error, ~"Unknown request type", State}. diff --git a/src/e9p_sysfs.erl b/src/e9p_sysfs.erl index 54ed810..ec6feda 100644 --- a/src/e9p_sysfs.erl +++ b/src/e9p_sysfs.erl @@ -10,24 +10,24 @@ -export([ init/1, - root/3, - walk/4, - open/4, - create/6, - read/5, - write/5, - remove/3, - stat/3, - wstat/4 + root/4, + walk/5, + open/5, + create/7, + read/6, + write/6, + remove/4, + stat/4, + wstat/5 ]). init(_State) -> {ok, []}. -root(_Uname, _Aname, State) -> +root(_Uname, _Aname, _UID, State) -> {ok, #{qid := QID}} = stat_for([]), {ok, {QID, []}, State}. -walk(_FID, Path, Name, State) -> +walk(_QID, _FState, Path, Name, State) -> ?LOG_DEBUG(#{path => Path, name => Name}), case stat_for(Path ++ [Name]) of {ok, #{qid := QID}} -> @@ -36,9 +36,9 @@ walk(_FID, Path, Name, State) -> {error, Reason, State} end. -open(_FID, [], _Mode, State) -> +open(_QID, _FState, [], _Mode, State) -> {ok, {[~"applications", ~"processes", ~"system_info"], 0}, State}; -open(_FID, [~"system_info"], _Mode, State) -> +open(_QID, _FState, [~"system_info"], _Mode, State) -> Keys = [ ~"allocated_areas", % ~"allocator", %% TBD @@ -121,14 +121,14 @@ open(_FID, [~"system_info"], _Mode, State) -> ~"wordsize" ], {ok, {Keys, 0}, State}; -open(_FID, [~"system_info", ~"wordsize"], _Mode, State) -> +open(_QID, _FState, [~"system_info", ~"wordsize"], _Mode, State) -> {ok, {[~"internal", ~"external"], 0}, State}; -open(_FID, [~"system_info", ~"wordsize", TypeB], _Mode, State) -> +open(_QID, _FState, [~"system_info", ~"wordsize", TypeB], _Mode, State) -> Type = binary_to_atom(TypeB), Wordsize = erlang:system_info({wordsize, Type}), Data = iolist_to_binary(io_lib:format("~B", [Wordsize])), {ok, {Data, 0}, State}; -open(_FID, [~"system_info", KeyB], _Mode, State) -> +open(_QID, _FState, [~"system_info", KeyB], _Mode, State) -> Key = binary_to_atom(KeyB), Data = case erlang:system_info(Key) of @@ -141,10 +141,10 @@ open(_FID, [~"system_info", KeyB], _Mode, State) -> end, {ok, {Data, 0}, State}; %% ===== Processes ===== -open(_FID, [~"processes"], _Mode, State) -> +open(_QID, _FState, [~"processes"], _Mode, State) -> Processes = lists:map(fun pid_to_list/1, erlang:processes()), {ok, {Processes, 0}, State}; -open(_FID, [~"processes", _PID], _Mode, State) -> +open(_QID, _FState, [~"processes", _PID], _Mode, State) -> Keys = [ ~"current_function", ~"initial_call", @@ -163,7 +163,7 @@ open(_FID, [~"processes", _PID], _Mode, State) -> ~"garbage_collection" ], {ok, {Keys, 0}, State}; -open(_FID, [~"processes", PIDB, KeyB], _Mode, State) -> +open(_QID, _FState, [~"processes", PIDB, KeyB], _Mode, State) -> PIDL = binary_to_list(PIDB), PID = list_to_pid(PIDL), Key = binary_to_existing_atom(KeyB), @@ -177,34 +177,34 @@ open(_FID, [~"processes", PIDB, KeyB], _Mode, State) -> {error, "No such file", State} end; %% ===== Applications ===== -open(_FID, [~"applications"], _Mode, State) -> +open(_QID, _FState, [~"applications"], _Mode, State) -> AllApps = lists:map( fun({Name, _, _}) when is_atom(Name) -> erlang:atom_to_binary(Name) end, application:loaded_applications() ), {ok, {AllApps, 0}, State}; -open(_FID, [~"applications", Name], _Mode, State) -> +open(_QID, _FState, [~"applications", Name], _Mode, State) -> Atom = binary_to_existing_atom(Name), {ok, AppKeys} = application:get_all_key(Atom), Keys = proplists:get_keys(AppKeys), Files = lists:map(fun erlang:atom_to_binary/1, Keys), {ok, {Files, 0}, State}; -open(_FID, [~"applications", Name, ~"env"], _Mode, State) -> +open(_QID, _FState, [~"applications", Name, ~"env"], _Mode, State) -> Atom = binary_to_existing_atom(Name), AllEnv = application:get_all_env(Atom), Data = iolist_to_binary(io_lib:format("%% coding: utf-8~n~n~p.", [AllEnv])), {ok, {Data, 0}, State}; -open(_FID, [~"applications", NameB, KeyB], _Mode, State) -> +open(_QID, _FState, [~"applications", NameB, KeyB], _Mode, State) -> Name = binary_to_existing_atom(NameB), Key = binary_to_existing_atom(KeyB), {ok, Val} = application:get_key(Name, Key), Data = iolist_to_binary(io_lib:format("~p", [Val])), {ok, {Data, 0}, State}. -create(_FID, _Path, _Name, _Perm, _Mode, State) -> +create(_QID, _FState, _Path, _Name, _Perm, _Mode, State) -> {error, "Not supported", State}. -read({QID, Data}, Path, Offset, Length, State) -> +read(QID, Data, Path, Offset, Length, State) -> case e9p:is_type(QID, directory) of true -> readdir(Data, Path, Offset, Length, State); false -> readfile(Data, Path, Offset, Length, State) @@ -214,7 +214,7 @@ readdir(Data, Path, Offset, Length, State) -> Encoded = lists:map( fun(Entry) -> {ok, Stat} = stat_for(Path ++ [Entry]), - e9p_msg:encode_stat(Stat) + e9p_msg:encode_stat(Stat, legacy) end, Data ), @@ -235,19 +235,19 @@ chunk(Data, Offset, Length) when ~"" end. -write(_FID, _Path, _Offset, _Data, State) -> +write(_QID, _FState, _Path, _Offset, _Data, State) -> {error, "Unimplemented", State}. -remove(_FID, _Path, State) -> +remove(_QID, _FState, _Path, State) -> {error, "Unimplemented", State}. -stat(_FID, Path, State) -> +stat(_QID, _FState, Path, State) -> case stat_for(Path) of {ok, Stat} -> {ok, Stat, State}; {error, Reason} -> {error, Reason, State} end. -wstat(_FID, _Path, _Stat, State) -> +wstat(_QID, _FState, _Path, _Stat, State) -> {error, "Not supported", State}. stat_for([]) -> diff --git a/src/e9p_unfs.erl b/src/e9p_unfs.erl index 870af76..7d3b2c0 100644 --- a/src/e9p_unfs.erl +++ b/src/e9p_unfs.erl @@ -15,16 +15,16 @@ Expose Unix Filesystem as 9p2000 mount -export([ init/1, - root/3, - walk/4, - stat/3, - open/4, - read/5, - clunk/2, - create/6, - write/5, - remove/3, - wstat/4 + root/4, + walk/5, + stat/4, + open/5, + read/6, + clunk/3, + create/7, + write/6, + remove/4, + wstat/5 ]). % Create QID and Stat data for given path. @@ -82,7 +82,7 @@ init(#{path := Path}) -> {ok, #{root => unicode:characters_to_binary(Path)}}. -doc false. -root(UName, AName, #{root := Root} = State) -> +root(UName, AName, _UID, #{root := Root} = State) -> ?LOG_INFO(#{uname => UName, aname => AName}), maybe {ok, Qid, _Stat} ?= qid(Root, []), @@ -90,19 +90,19 @@ root(UName, AName, #{root := Root} = State) -> end. -doc false. -walk(_QID, Path, ~"..", #{root := Root} = State) -> +walk(_QID, _FState, Path, ~"..", #{root := Root} = State) -> case qid(Root, lists:droplast(Path)) of {ok, NQid, _Stat} -> {{NQid, []}, State}; {error, _} -> {false, State} end; -walk(_QID, Path, File, #{root := Root} = State) -> +walk(_QID, _FStat, Path, File, #{root := Root} = State) -> case qid(Root, Path ++ [File]) of {ok, NQid, _Stat} -> {{NQid, []}, State}; {error, _} -> {false, State} end. -doc false. -stat({QID, _}, Path, #{root := Root} = State) -> +stat(QID, _, Path, #{root := Root} = State) -> FullPath = filename:join([Root] ++ Path), case file:read_file_info(FullPath, [{time, posix}, raw]) of {ok, FileInfo} -> @@ -113,7 +113,7 @@ stat({QID, _}, Path, #{root := Root} = State) -> end. -doc false. -wstat(_QID, Path, Stat, #{root := Root} = State) -> +wstat(_QID, _FStat, Path, Stat, #{root := Root} = State) -> FileInfo = stat_to_file_info(Stat), FullPath = filename:join([Root] ++ Path), @@ -123,7 +123,7 @@ wstat(_QID, Path, Stat, #{root := Root} = State) -> end. -doc false. -open({QID, []}, Path, Mode, #{root := Root} = State) -> +open(QID, [], Path, Mode, #{root := Root} = State) -> FullPath = filename:join([Root] ++ Path), QS = case e9p:is_type(QID, directory) of @@ -148,20 +148,20 @@ open({QID, []}, Path, Mode, #{root := Root} = State) -> {ok, {QS, 0}, State}. -doc false. -clunk({_, {regular, FD}}, State) -> +clunk(_, {regular, FD}, State) -> ok = file:close(FD), {ok, State}; -clunk(_QID, State) -> +clunk(_QID, _FStat, State) -> {ok, State}. -doc false. -create(_QID, _Path, _Name, _Perm, _Mode, State) -> +create(_QID, _FStat, _Path, _Name, _Perm, _Mode, State) -> {error, "Unsupported", State}. -doc false. -remove({QID, _} = FID, Path, #{root := Root} = State0) -> +remove(QID, FState, Path, #{root := Root} = State0) -> FullPath = filename:join([Root] ++ Path), - {ok, State} = clunk(FID, State0), + {ok, State} = clunk(QID, FState, State0), case case e9p:is_type(QID, directory) of % Currently `file` module do not expose raw mode for listing @@ -191,13 +191,13 @@ translate_mode([exec]) -> {false, [read]}. -doc false. -read({_QID, {regular, FD}}, _Path, Offset, Len, State) -> +read(_QID, {regular, FD}, _Path, Offset, Len, State) -> case file:pread(FD, Offset, Len) of {ok, Data} -> {ok, {{regular, FD}, Data}, State}; eof -> {ok, {{regular, FD}, []}, State}; {error, Err} -> {error, Err, State} end; -read({_QID, {dir, List}}, Path, _Offset, Len, #{root := Root} = State) -> +read(_QID, {dir, List}, Path, _Offset, Len, #{root := Root} = State) -> {Remaining, Data} = readdir(Root, Path, List, Len, []), {ok, {{dir, Remaining}, Data}, State}. @@ -215,7 +215,7 @@ readdir(Root, Path, [Next | Rest], Len, Acc) -> end. -doc false. -write({_QID, {regular, FD}}, _Path, Offset, Data, 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} diff --git a/test/e9p_msg_SUITE.erl b/test/e9p_msg_SUITE.erl index 3cda894..449f7fd 100644 --- a/test/e9p_msg_SUITE.erl +++ b/test/e9p_msg_SUITE.erl @@ -35,7 +35,7 @@ stat_encode_decode(_Conf) -> gid => <<>>, muid => <<>> }, - Out = iolist_to_binary(e9p_msg:encode_stat(Stat)), + Out = iolist_to_binary(e9p_msg:encode_stat(Stat, legacy)), Decoded = e9p_msg:parse_stat(Out), ?assertEqual({ok, Stat}, Decoded).