diff --git a/TODO b/TODO index fed003f..8041787 100644 --- a/TODO +++ b/TODO @@ -1,24 +1,36 @@ - - zomp nodes must report the realms they provide to upstream nodes to which they connect. - On redirect a list of hosts of the form [{zx:host(), [zx:realm()]}] must be provided to the downstream client. - This is the only way that downstream clients can determine which redirect hosts are useful to it. +- Make the create project command fail if the realm is invalid or the project bame already exists. - - Change zx_daemon request references to counters. - Count everything. - This will be the only way to sort of track stats other than log analysis. - Log analysis sucks. +- Add a module name conflict checker to ZX - - Double-indexing must happen everywhere for anything to be discoverable without traversing the entire state of zx_daemon whenever a client or conn crashes. +- Check whether the "repo name doesn't have to be the same as package, package doesn't have to be the same as main interface module" statement is true. - - zx_daemon request() types have been changes to flat, wide tuples as in the main zx module. - This change is not yet refected in the using code. +- Make the "create user" bundle command check whether a user already exists at that realm. - - Write a logging process. - Pick a log rotation scheme. - Eventually make it so that it can shed log loads in the event they get out of hand. +- Define the user bundle file contents: .zuf +- Make it possible for a site administrator to declare specific versions for programs executed by clients. + Site adminisration via an organizational mirror should not be ricket science. + +- zomp nodes must report the realms they provide to upstream nodes to which they connect. + On redirect a list of hosts of the form [{zx:host(), [zx:realm()]}] must be provided to the downstream client. + This is the only way that downstream clients can determine which redirect hosts are useful to it. + +- Write a logging process. + Pick a log rotation scheme. + Eventually make it so that it can shed log loads in the event they get out of hand. + +- Create zx_daemon primes. + See below + +- Create persistent Windows alias for "zx" using the doskey command and adding it to the localuser's registry. + The installation escript will need to be updated to update the windows registry for HKEY_CURRENT_USER\Software\Microsoft\Command Processor + AutoRun will need to be set for %USERPROFILE%\zomp_alias.cmd + zomp_alias.cmd will need to do something like: + `doskey zx="werl.exe -pa %zx_dir%/ebin -run zx start $*"` + https://stackoverflow.com/questions/20530996/aliases-in-windows-command-prompt + Whatever happens, local users must be able to do `zx $@` and get the expected results. + Somewhat more tricky is how to make the creation of shortcuts less taxing on the lay user... ? - - Create zx_daemon primes. - See below New Feature: ZX Universal lock Cross-instance communication diff --git a/Zomp Protocol Specification.odt b/Zomp Protocol Specification.odt new file mode 100644 index 0000000..2e61180 Binary files /dev/null and b/Zomp Protocol Specification.odt differ diff --git a/zomp/lib/otpr-zx/0.1.0/src/zx.erl b/zomp/lib/otpr-zx/0.1.0/src/zx.erl index 306bb91..e8c1414 100644 --- a/zomp/lib/otpr-zx/0.1.0/src/zx.erl +++ b/zomp/lib/otpr-zx/0.1.0/src/zx.erl @@ -2492,6 +2492,7 @@ usage() -> " zx add package PackageName~n" " zx add packager PackageName~n" " zx add maintainer PackageName~n" + " zx add sysop UserID~n" " zx review PackageID~n" " zx approve PackageID~n" " zx reject PackageID~n" diff --git a/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl b/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl index ec6644f..119c27e 100644 --- a/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl +++ b/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl @@ -1,4 +1,4 @@ -%%% @doc +%% @doc %%% ZX Connector %%% %%% This module represents a connection to a Zomp server. @@ -11,12 +11,59 @@ -copyright("Craig Everett "). -license("GPL-3.0"). +-export([subscribe/2, unsubscribe/2, fetch/3, query/3]). -export([start/1, stop/1]). --export([start_link/1]). +-export([start_link/1, init/2]). --include("zx_logger.erl"). +-include("zx_logger.hrl"). +%%% Types + +-type incoming() :: ping + | {sub, Channel :: term(), Message :: term()} + | term(). + + +%%% Interface + +-spec subscribe(Conn, Package) -> ok + when Conn :: pid(), + Package :: zx:package(). + +subscribe(Conn, Realm) -> + Conn ! {subscribe, Realm}, + ok. + + +-spec unsubscribe(Conn, Package) -> ok + when Conn :: pid(), + Package :: zx:package(). + +unsubscribe(Conn, Package) -> + Conn ! {unsubscribe, Package}, + ok. + + +-spec fetch(Conn, ID, Object) -> ok + when Conn :: pid(), + ID :: zx_daemon:id(), + Object :: zx_daemon:object(). + +fetch(Conn, ID, Object) -> + Conn ! {fetch, ID, Object}, + ok. + + +-spec query(Conn, ID, Action) -> ok + when Conn :: pid(), + ID :: zx_daemon:id(), + Action :: zx_daemon:action(). + +query(Conn, ID, Action) -> + Conn ! {query, ID, Action}, + ok. + %%% Startup @@ -42,25 +89,7 @@ stop(Conn) -> ok. --spec subscribe(Conn, Package) -> ok - when Conn :: pid(), - Package :: zx:package(). - -subscribe(Conn, Realm) -> - Conn ! {subscribe, Realm}, - ok. - - --spec unsubscribe(Conn, Package) -> ok - when Conn :: pid(), - Package :: zx:package(). - -unsubscribe(Conn, Package) -> - Conn ! {unsubscribe, Package}, - ok. - - --spec start_link(Target) -> +-spec start_link(Target) -> Result when Target :: zx:host(), Result :: {ok, pid()} | {error, Reason}, @@ -94,13 +123,13 @@ init(Parent, Target) -> Target :: zx:host(). connect(Parent, Debug, {Host, Port}) -> - Options = [{packet, 4}, {mode, binary}, {active, true}], + Options = [{packet, 4}, {mode, binary}, {nodelay, true}, {active, true}], case gen_tcp:connect(Host, Port, Options, 5000) of {ok, Socket} -> confirm_service(Parent, Debug, Socket); {error, Error} -> - ok = log(warning, "Connection problem with ~tp: ~tp", [Node, Error]), - ok = zx_daemon:report(disconnected) + ok = log(warning, "Connection problem with ~tp: ~tp", [Host, Error]), + ok = zx_daemon:report(failed), terminate() end. @@ -170,12 +199,23 @@ query_realms(Parent, Debug, Socket) -> loop(Parent, Debug, Socket) -> receive {tcp, Socket, Bin} -> - ok = handle(Bin, Socket), + ok = handle_message(Socket, Bin), ok = inet:setopts(Socket, [{active, once}]), loop(Parent, Debug, Socket); {subscribe, Package} -> + ok = zx_net:send(Socket, {subscribe, Package}), + loop(Parent, Debug, Socket); {unsubscribe, Package} -> - + ok = zx_net:send(Socket, {unsubscribe, Package}), + loop(Parent, Debug, Socket); + {fetch, ID, Object} -> + {ok, Outcome} = handle_fetch(Socket, Object), + ok = zx_daemon:result(ID, Outcome), + loop(Parent, Debug, Socket); + {query, ID, Action} -> + {ok, Outcome} = handle_query(Socket, Action), + ok = zx_daemon:result(ID, Outcome), + loop(Parent, Debug, Socket); stop -> ok = zx_net:disconnect(Socket), terminate(); @@ -185,41 +225,106 @@ loop(Parent, Debug, Socket) -> end. --spec handle(Bin, Socket) -> ok | no_return() - when Bin :: binary(), - Socket :: gen_tcp:socket(). + +%%% Idle Incoming Upstream Messages + +-spec handle_message(Socket, Bin) -> ok | no_return() + when Socket :: gen_tcp:socket(), + Bin :: binary(). %% @private %% Single point to convert a binary message to a safe internal message. Actual handling %% of the converted message occurs in dispatch/2. -handle(Bin, Socket) -> +handle_message(Socket, Bin) -> Message = binary_to_term(Bin, [safe]), ok = log(info, "Received network message: ~tp", [Message]), - dispatch(Message, Socket). + case binary_to_term(Bin, [safe]) of + ping -> + zx_net:send(Socket, pong); + {sub, Channel, Message} -> + log("Sub: ~tp - ~tp", [Channel, Message]); + {update, Message} -> + log("Update: ~tp", [Message]); + {redirect, Nodes} -> + log("Redirected to ~tp", [Nodes]); + Invalid -> + {ok, {Addr, Port}} = zomp:peername(Socket), + Host = inet:ntoa(Addr), + ok = log(warning, "Invalid message from ~tp:~p: ", [Invalid]), + ok = zx_net:disconnect(Socket), + terminate() + end. --spec dispatch(Message, Socket) -> ok | no_return() - when Message :: incoming(), - Socket :: gen_tcp:socket(). -%% @private -%% Dispatch a procedure based on the received message. -%% Tranfers and other procedures that involve a sequence of messages occur in discrete -%% states defined in other functions -- this only dispatches based on a valid initial -%% message received in the default waiting-loop state. -dispatch(ping, Socket) -> - zx_net:send(Socket, pong); -dispatch(Invalid, Socket) -> - {ok, {Addr, Port}} = zomp:peername(Socket), - Host = inet:ntoa(Addr), - ok = log(warning, "Invalid message from ~tp:~p: ", [Invalid]), - ok = zx_net:disconnect(Socket), - terminate(). +%%% Incoming Request Actions + +-spec handle_request(Socket, Action) -> Result + when Socket :: gen_tcp:socket(), + Action :: term(), + Result :: {ok, Outcome :: term()}. + +handle_request(Socket, Action) -> + ok = zx_net:send(Socket, Action), + case element(1, Action) of + list -> + do_list(Action, Socket); + latest -> + do_latest(Action, Socket); + fetch -> + do_fetch(Action, Socket); + key -> + do_key(Action, Socket); + pending -> + do_pending(Action, Socket); + packagers -> + do_packagers(Action, Socket); + maintainers -> + do_maintainers(Action, Socket); + sysops -> + do_sysops(Action, Socket) + end, + handle_response(Socket, Response). + + + +handle_response(Socket, Command) -> + receive + {tcp, Socket, Bin} -> + Outcome = binary_to_term(Bin, [safe]), + interpret_response(Socket, Outcome, Command); + {tcp_closed, Socket} -> + handle_unexpected_close() + after 5000 -> + handle_timeout(Socket) + end. + + +interpret_response(Socket, ping, Command) -> + ok = zx_net:send(Socket, pong), + handle_response(Socket, Command); +interpret_response(Socket, {sub, Channel, Message}, Command) -> + ok = zx_daemon:notify(Channel, Message), + handle_response(Socket, Command); +interpret_response(Socket, {update, Message}, Command) -> +interpret_response(Socket, Response, list) -> +interpret_response(Socket, Response, latest) -> +interpret_response(Socket, Response, fetch) -> +interpret_response(Socket, Response, key) -> +interpret_response(Socket, Response, pending) -> +interpret_response(Socket, Response, packagers) -> +interpret_response(Socket, Response, maintainers) -> +interpret_response(Socket, Response, sysops) -> + + + + case element(1, Action) of + end, -spec fetch(Socket, PackageID) -> Result when Socket :: gen_tcp:socket(), - PackageID :: package_id(), + PackageID :: zx:package_id(), Result :: ok. %% @private %% Download a package to the local cache. @@ -227,13 +332,14 @@ dispatch(Invalid, Socket) -> fetch(Socket, PackageID) -> {ok, LatestID} = request_zrp(Socket, PackageID), ok = receive_zrp(Socket, LatestID), - log(info, "Fetched ~ts", [package_string(LatestID)]). + Latest = zx_lib:package_string(LatestID), + log(info, "Fetched ~ts", [Latest]). -spec request_zrp(Socket, PackageID) -> Result when Socket :: gen_tcp:socket(), - PackageID :: package_id(), - Result :: {ok, Latest :: package_id()} + PackageID :: zx:package_id(), + Result :: {ok, Latest :: zx:package_id()} | {error, Reason :: timeout | term()}. request_zrp(Socket, PackageID) -> @@ -244,7 +350,7 @@ request_zrp(Socket, PackageID) -> {sending, LatestID} -> {ok, LatestID}; Error = {error, Reason} -> - PackageString = package_string(PackageID), + PackageString = zx_lib:package_string(PackageID), Message = "Error receiving package ~ts: ~tp", ok = log(info, Message, [PackageString, Reason]), Error @@ -258,7 +364,7 @@ request_zrp(Socket, PackageID) -> -spec receive_zrp(Socket, PackageID) -> Result when Socket :: gen_tcp:socket(), - PackageID :: package_id(), + PackageID :: zx:package_id(), Result :: ok | {error, timeout}. receive_zrp(Socket, PackageID) -> @@ -286,11 +392,11 @@ handle_unexpected_close() -> terminate(). --spec handle_timeout(gen_tcp:socket()) -> no_return() +-spec handle_timeout(gen_tcp:socket()) -> no_return(). handle_timeout(Socket) -> ok = zx_daemon:report(timeout), - ok = disconnect(Socket), + ok = zx_net:disconnect(Socket), terminate(). diff --git a/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl b/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl index b078f0b..cc78934 100644 --- a/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl +++ b/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl @@ -218,7 +218,9 @@ %% Conn Communication -type conn_report() :: {connected, Realms :: [{zx:realm(), zx:serial()}]} | {redirect, Hosts :: [zx:host()]} - | disconnected. + | failed + | disconnected + | timeout. %% Subscriber / Requestor Communication % Incoming Request messages @@ -229,8 +231,9 @@ | {list, zx:realm(), zx:name(), zx:version()} | {latest, zx:realm(), zx:name(), zx:version()} | {fetch, zx:realm(), zx:name(), zx:version()} - | {key, zx:realm(), zx:key_name()} + | {fetchkey, zx:realm(), zx:key_name()} | {pending, zx:realm(), zx:name()} + | {resigns, zx:realm()} | {packagers, zx:realm(), zx:name()} | {maintainers, zx:realm(), zx:name()} | {sysops, zx:realm()}. @@ -447,14 +450,14 @@ latest({Realm, Name, Version}) -> %% Response messages are of the type `result()' where the third element is of the %% type `fetch_result()'. -fetch({Realm, Name, Version}) -> +fetch_zsp(PackageID = {Realm, Name, Version}) -> true = zx_lib:valid_lower0_9(Realm), true = zx_lib:valid_lower0_9(Name), true = zx_lib:valid_version(Version), - request({fetch, Realm, Name, Version}). + request({fetch, zsp, PackageID}). --spec key(KeyID) -> {ok, RequestID} +-spec fetch_key(KeyID) -> {ok, RequestID} when KeyID :: zx:key_id(), RequestID :: id(). %% @doc @@ -464,10 +467,10 @@ fetch({Realm, Name, Version}) -> %% Response messages are of the type `result()' where the third element is of the %% type `key_result()'. -key({Realm, KeyName}) -> +fetch_key(KeyID = {Realm, KeyName}) -> true = zx_lib:valid_lower0_9(Realm), true = zx_lib:valid_lower0_9(KeyName), - request({key, Realm, KeyName}). + request({fetch, key, KeyID}). -spec pending(Package) -> {ok, RequestID} @@ -561,7 +564,7 @@ report(Message) -> gen_server:cast(?MODULE, {report, self(), Message}). --spec result(reference(), result()) -> ok. +-spec result(id(), result()) -> ok. %% @private %% Return a tagged result back to the daemon to be forwarded to the original requestor. @@ -839,6 +842,10 @@ do_report(Conn, failed, State = #s{mx = MX}) -> NewMX = mx_del_monitor(Conn, attempt, MX), failed(Conn, State#s{mx = NewMX}); do_report(Conn, disconnected, State = #s{mx = MX}) -> + NewMX = mx_del_monitor(Conn, conn, MX), + disconnected(Conn, State#s{mx = NewMX}); +do_report(Conn, timeout, State = #s{mx = MX}) -> + ok = log(warning, "Connection ~tp timed out.", [Conn]), NewMX = mx_del_monitor(Conn, conn, MX), disconnected(Conn, State#s{mx = NewMX}). @@ -1109,7 +1116,7 @@ dispatch_request(Action, ID, CX) -> Realm = element(2, Action), case cx_pre_send(Realm, ID, CX) of {ok, Conn, NewCX} -> - ok = zx_conn:make_request(Conn, ID, Action), + ok = zx_conn:request(Conn, ID, Action), {dispatched, NewCX}; unassigned -> wait;