Merge branch 'dev' into 'master'
Half un-borked See merge request zxq9/zx!13
This commit is contained in:
commit
0bf9620eb3
@ -1,5 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script prepares a general test environment.
|
||||
# If it is invoked as $(path/to/script) the first time it is run the necessary
|
||||
# environment variable will be exported.
|
||||
|
||||
pushd $(dirname $BASH_SOURCE) > /dev/null
|
||||
ZX_DEV_ROOT=$PWD
|
||||
popd > /dev/null
|
||||
@ -7,4 +11,4 @@ ZOMP_DIR="$ZX_DEV_ROOT/tester"
|
||||
rm -rf "$ZOMP_DIR"
|
||||
cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR"
|
||||
|
||||
echo "Done. Make sure to export \"export ZOMP_DIR=$ZOMP_DIR\" before running zx"
|
||||
echo "export ZOMP_DIR=$ZOMP_DIR"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{realm, "otpr"}.
|
||||
{prime, {"otpr.psychobitch.party",11311}}.
|
||||
{prime, {"zomp.psychobitch.party",11311}}.
|
||||
{sysop, "zxq9"}.
|
||||
{key, "zxq9-root"}.
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
{application, zx,
|
||||
[{description, "Zomp client program"},
|
||||
{vsn, "0.1.0"},
|
||||
{applications, [stdlib, kernel]},
|
||||
{modules, [zx,
|
||||
zx_sup,
|
||||
zx_daemon,
|
||||
zx_conn_sup,
|
||||
zx_conn,
|
||||
zx_lib,
|
||||
zx_net]},
|
||||
{mod, {zx, none}}]}.
|
||||
{application,zx,
|
||||
[{description,"Zomp client program"},
|
||||
{vsn,"0.1.0"},
|
||||
{applications,[stdlib,kernel]},
|
||||
{modules,[zx,zx_auth,zx_conn,zx_conn_sup,zx_daemon,zx_key,
|
||||
zx_lib,zx_local,zx_net,zx_sup,zx_sys_conf,zx_tty]},
|
||||
{mod,{zx,none}}]}.
|
||||
|
||||
@ -31,6 +31,6 @@ log(Level, Format, Args) ->
|
||||
warning -> "[WARNING]";
|
||||
error -> "[ERROR]"
|
||||
end,
|
||||
Out = io_lib:format("~s ~p: " ++ Format ++ "~n", [Tag, self() | Args]),
|
||||
Out = io_lib:format("~s ~w ~w: " ++ Format ++ "~n", [Tag, ?MODULE, self() | Args]),
|
||||
UTF8 = unicode:characters_to_binary(Out),
|
||||
io:format(UTF8).
|
||||
|
||||
@ -4,9 +4,14 @@
|
||||
|
||||
main(Args) ->
|
||||
true = code:add_patha("ebin"),
|
||||
up_to_date = make:all(),
|
||||
ok = lists:foreach(fun dispatch/1, Args),
|
||||
halt(0).
|
||||
case make:all() of
|
||||
up_to_date ->
|
||||
lists:foreach(fun dispatch/1, Args),
|
||||
halt(0);
|
||||
error ->
|
||||
ok = io:format("Build error. Halting.~n"),
|
||||
halt(1)
|
||||
end.
|
||||
|
||||
|
||||
dispatch("edoc") ->
|
||||
|
||||
@ -1,15 +1,26 @@
|
||||
%%% @doc
|
||||
%%% ZX
|
||||
%%% ZX: A suite of tools for Erlang development and deployment.
|
||||
%%%
|
||||
%%% A general dependency and packaging tool that works together with the zomp
|
||||
%%% package manager. Given a project directory with a standard layout, zx can:
|
||||
%%% - Initialize your project for packaging and semver tracking under zomp.
|
||||
%%% - Add dependencies (recursively) defined in any zomp repository realm.
|
||||
%%% - Update dependencies (recursively) defined in any zomp repository realm.
|
||||
%%% - Remove dependencies.
|
||||
%%% - Update, upgrade or run any application from source that zomp tracks.
|
||||
%%% ZX can:
|
||||
%%% - Create project templates for applications, libraries and escripts.
|
||||
%%% - Initialize existing projects for packaging and management.
|
||||
%%% - Create and manage zomp realms, users, keys, etc.
|
||||
%%% - Manage dependencies hosted in any zomp realm.
|
||||
%%% - Package, submit, pull-for-review, and resign-to-accept packages.
|
||||
%%% - Update, upgrade, and run any application from source that zomp tracks.
|
||||
%%% - Locally install packages from files and locally stored public keys.
|
||||
%%% - Build and run a local project from source using zomp dependencies.
|
||||
%%% - Start an anonymous zomp distribution node.
|
||||
%%% - Act as a unified code launcher for any projects (Erlang + ZX = deployed).
|
||||
%%%
|
||||
%%% ZX is currently limited in one specific way:
|
||||
%%% - Can only launch pure Erlang code.
|
||||
%%%
|
||||
%%% In the works:
|
||||
%%% - Support for LFE
|
||||
%%% - Support for Rust (cross-platform)
|
||||
%%% - Support for Elixir (as a peer language)
|
||||
%%% - Unified Windows installer to deploy Erlang, Rust, LFE, Elixir and ZX
|
||||
%%% @end
|
||||
|
||||
-module(zx).
|
||||
@ -30,7 +41,7 @@
|
||||
key_id/0, key_name/0,
|
||||
user_id/0, user_name/0, contact_info/0, user_data/0,
|
||||
lower0_9/0, label/0,
|
||||
package_meta/0,
|
||||
package_meta/0, ss_tag/0,
|
||||
outcome/0]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
@ -48,20 +59,24 @@
|
||||
Minor :: non_neg_integer() | z,
|
||||
Patch :: non_neg_integer() | z}.
|
||||
-type host() :: {string() | inet:ip_address(), inet:port_number()}.
|
||||
-type key_data() :: {Name :: key_name(),
|
||||
Public :: none | {SHA512 :: binary(), DER :: binary()},
|
||||
Private :: none | {SHA512 :: binary(), DER :: binary()}}.
|
||||
-type key_id() :: {realm(), key_name()}.
|
||||
-type key_name() :: lower0_9().
|
||||
-type user_id() :: {realm(), user_name()}.
|
||||
-type user_name() :: label().
|
||||
-type contact_info() :: {Type :: string(), Data :: string()}.
|
||||
-type user_data() :: {ID :: user_id(),
|
||||
RealName :: string(),
|
||||
Contact :: [contact_info()],
|
||||
Keys :: [key_name()]}.
|
||||
Keys :: [key_data()]}.
|
||||
-type user_id() :: {realm(), user_name()}.
|
||||
-type user_name() :: label().
|
||||
-type contact_info() :: {Type :: string(), Data :: string()}.
|
||||
-type lower0_9() :: [$a..$z | $0..$9 | $_].
|
||||
-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
||||
-type package_meta() :: #{package_id := package_id(),
|
||||
deps := [package_id()],
|
||||
type := app | lib}.
|
||||
-type ss_tag() :: {serial(), calendar:timestamp()}.
|
||||
|
||||
-type outcome() :: ok
|
||||
| {error, Reason :: term()}
|
||||
@ -75,6 +90,7 @@
|
||||
-spec do() -> no_return().
|
||||
|
||||
do() ->
|
||||
ok = io:setopts([{encoding, unicode}]),
|
||||
do([]).
|
||||
|
||||
|
||||
@ -83,44 +99,16 @@ do() ->
|
||||
%% Dispatch work functions based on the nature of the input arguments.
|
||||
|
||||
do(["help"]) ->
|
||||
usage_exit(0);
|
||||
do(["run", PackageString | Args]) ->
|
||||
done(help(top));
|
||||
do(["help", "user"]) ->
|
||||
done(help(user));
|
||||
do(["help", "dev"]) ->
|
||||
done(help(dev));
|
||||
do(["help", "sysop"]) ->
|
||||
done(help(sysop));
|
||||
do(["run", PackageString | ArgV]) ->
|
||||
ok = start(),
|
||||
run(PackageString, Args);
|
||||
do(["runlocal" | ArgV]) ->
|
||||
ok = start(),
|
||||
run_local(ArgV);
|
||||
do(["init", "app", PackageString]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
done(zx_local:initialize(app, PackageString));
|
||||
do(["init", "lib", PackageString]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
done(zx_local:initialize(lib, PackageString));
|
||||
do(["list", "deps"]) ->
|
||||
done(zx_local:list_deps());
|
||||
do(["list", "deps", PackageString]) ->
|
||||
done(zx_local:list_deps(PackageString));
|
||||
do(["install", PackageFile]) ->
|
||||
ok = start(),
|
||||
done(zx_daemon:install(PackageFile));
|
||||
do(["set", "timeout", String]) ->
|
||||
done(zx_local:set_timeout(String));
|
||||
do(["add", "mirror"]) ->
|
||||
done(zx_local:add_mirror());
|
||||
do(["drop", "mirror"]) ->
|
||||
done(zx_local:drop_mirror());
|
||||
do(["status"]) ->
|
||||
done(zx_local:status());
|
||||
do(["set", "dep", PackageString]) ->
|
||||
done(zx_local:set_dep(PackageString));
|
||||
do(["set", "version", VersionString]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
done(zx_local:set_version(VersionString));
|
||||
do(["verup", Level]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
done(zx_local:verup(Level));
|
||||
do(["update", ".app"]) ->
|
||||
done(zx_local:update_app_file());
|
||||
not_done(run(PackageString, ArgV));
|
||||
do(["list", "realms"]) ->
|
||||
done(zx_local:list_realms());
|
||||
do(["list", "packages", Realm]) ->
|
||||
@ -134,67 +122,121 @@ do(["latest", PackageString]) ->
|
||||
done(zx_local:latest(PackageString));
|
||||
do(["import", "realm", RealmFile]) ->
|
||||
done(zx_local:import_realm(RealmFile));
|
||||
do(["drop", "realm", Realm]) ->
|
||||
done(zx_local:drop_realm(Realm));
|
||||
do(["logpath", Package, Run]) ->
|
||||
done(zx_local:logpath(Package, Run));
|
||||
do(["status"]) ->
|
||||
done(zx_local:status());
|
||||
do(["set", "timeout", String]) ->
|
||||
done(zx_local:set_timeout(String));
|
||||
do(["add", "mirror"]) ->
|
||||
done(zx_local:add_mirror());
|
||||
do(["drop", "mirror"]) ->
|
||||
done(zx_local:drop_mirror());
|
||||
do(["create", "project"]) ->
|
||||
done(zx_local:create_project());
|
||||
do(["runlocal" | ArgV]) ->
|
||||
ok = start(),
|
||||
not_done(run_local(ArgV));
|
||||
do(["init"]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
done(zx_local:initialize());
|
||||
do(["list", "deps"]) ->
|
||||
done(zx_local:list_deps());
|
||||
do(["list", "deps", PackageString]) ->
|
||||
done(zx_local:list_deps(PackageString));
|
||||
do(["set", "dep", PackageString]) ->
|
||||
done(zx_local:set_dep(PackageString));
|
||||
do(["drop", "dep", PackageString]) ->
|
||||
PackageID = zx_lib:package_id(PackageString),
|
||||
done(zx_local:drop_dep(PackageID));
|
||||
do(["verup", Level]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
done(zx_local:verup(Level));
|
||||
do(["set", "version", VersionString]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
done(zx_local:set_version(VersionString));
|
||||
do(["update", ".app"]) ->
|
||||
done(zx_local:update_app_file());
|
||||
do(["create", "plt"]) ->
|
||||
done(zx_local:create_plt());
|
||||
do(["dialyze"]) ->
|
||||
done(zx_local:dialyze());
|
||||
do(["package"]) ->
|
||||
{ok, TargetDir} = file:get_cwd(),
|
||||
zx_local:package(TargetDir);
|
||||
done(zx_local:package(TargetDir));
|
||||
do(["package", TargetDir]) ->
|
||||
case filelib:is_dir(TargetDir) of
|
||||
true -> done(zx_local:package(TargetDir));
|
||||
false -> done({error, "Target directory does not exist", 22})
|
||||
end;
|
||||
do(["dialyze"]) ->
|
||||
done(zx_local:dialyze());
|
||||
do(["submit", PackageFile]) ->
|
||||
done(zx_auth:submit(PackageFile));
|
||||
do(["list", "pending", PackageName]) ->
|
||||
done(zx_auth:list_pending(PackageName));
|
||||
do(["list", "approved", Realm]) ->
|
||||
done(zx_auth:list_approved(Realm));
|
||||
do(["review", PackageString]) ->
|
||||
done(zx_auth:review(PackageString));
|
||||
do(["approve", PackageString]) ->
|
||||
done(zx_auth:approve(PackageString));
|
||||
do(["reject", PackageString]) ->
|
||||
done(zx_auth:reject(PackageString));
|
||||
do(["add", "key"]) ->
|
||||
done(zx_auth:add_key());
|
||||
do(["create", "user"]) ->
|
||||
done(zx_local:create_user());
|
||||
do(["create", "userfile"]) ->
|
||||
done(zx_local:create_userfile());
|
||||
do(["create", "keypair"]) ->
|
||||
done(zx_local:grow_a_pair());
|
||||
do(["export", "user"]) ->
|
||||
done(zx_local:export_user());
|
||||
do(["import", "user", ZdufFile]) ->
|
||||
done(zx_local:import_user(ZdufFile));
|
||||
do(["create", "keypair"]) ->
|
||||
done(zx_local:grow_a_pair());
|
||||
do(["drop", "key", Realm, KeyName]) ->
|
||||
done(zx_local:drop_key({Realm, KeyName}));
|
||||
do(["create", "plt"]) ->
|
||||
done(zx_local:create_plt());
|
||||
do(["create", "realm"]) ->
|
||||
done(zx_local:create_realm());
|
||||
do(["list", "packagers", PackageName]) ->
|
||||
done(zx_auth:list_packagers(PackageName));
|
||||
do(["list", "maintainers", PackageName]) ->
|
||||
done(zx_auth:list_maintainers(PackageName));
|
||||
do(["list", "sysops", Realm]) ->
|
||||
done(zx_auth:list_sysops(Realm));
|
||||
do(["create", "realmfile"]) ->
|
||||
done(zx_local:create_realmfile());
|
||||
do(["install", PackageFile]) ->
|
||||
case filelib:is_regular(PackageFile) of
|
||||
true ->
|
||||
ok = start(),
|
||||
done(zx_daemon:install(PackageFile));
|
||||
false ->
|
||||
done({error, "Target directory does not exist", 22})
|
||||
end;
|
||||
do(["accept", PackageString]) ->
|
||||
done(zx_auth:accept(PackageString));
|
||||
do(["add", "package", PackageName]) ->
|
||||
done(zx_auth:add_package(PackageName));
|
||||
do(["list", "users", Realm]) ->
|
||||
done(zx_auth:list_users(Realm));
|
||||
do(["add", "user", ZpuFile]) ->
|
||||
done(zx_auth:add_user(ZpuFile));
|
||||
do(["rem", "user", ZpuFile]) ->
|
||||
done(zx_auth:rem_user(ZpuFile));
|
||||
do(["add", "packager", Package, UserName]) ->
|
||||
done(zx_auth:add_packager(Package, UserName));
|
||||
do(["rem", "packager", Package, UserName]) ->
|
||||
done(zx_auth:rem_packager(Package, UserName));
|
||||
do(["add", "maintainer", Package, UserName]) ->
|
||||
done(zx_auth:add_maintainer(Package, UserName));
|
||||
do(["rem", "maintainer", Package, UserName]) ->
|
||||
done(zx_auth:rem_maintainer(Package, UserName));
|
||||
do(["add", "sysop", Package, UserName]) ->
|
||||
done(zx_auth:add_sysop(Package, UserName));
|
||||
do(["create", "realm"]) ->
|
||||
done(zx_local:create_realm());
|
||||
do(["takeover", Realm]) ->
|
||||
done(zx_local:takeover(Realm));
|
||||
do(["abdicate", Realm]) ->
|
||||
done(zx_local:abdicate(Realm));
|
||||
do(["drop", "realm", Realm]) ->
|
||||
done(zx_local:drop_realm(Realm));
|
||||
do(["list", "pending", PackageName]) ->
|
||||
done(zx_auth:list_pending(PackageName));
|
||||
do(["list", "resigns", Realm]) ->
|
||||
done(zx_auth:list_resigns(Realm));
|
||||
do(["submit", PackageFile]) ->
|
||||
done(zx_auth:submit(PackageFile));
|
||||
do(["review", PackageString]) ->
|
||||
done(zx_auth:review(PackageString));
|
||||
do(["approve", PackageString]) ->
|
||||
PackageID = zx_lib:package_id(PackageString),
|
||||
done(zx_auth:approve(PackageID));
|
||||
do(["reject", PackageString]) ->
|
||||
PackageID = zx_lib:package_id(PackageString),
|
||||
done(zx_auth:reject(PackageID));
|
||||
do(["accept", PackageString]) ->
|
||||
done(zx_auth:accept(PackageString));
|
||||
do(["add", "packager", Package, UserName]) ->
|
||||
done(zx_auth:add_packager(Package, UserName));
|
||||
do(["add", "maintainer", Package, UserName]) ->
|
||||
done(zx_auth:add_maintainer(Package, UserName));
|
||||
do(["add", "sysop", Package, UserName]) ->
|
||||
done(zx_auth:add_sysop(Package, UserName));
|
||||
do(["add", "package", PackageName]) ->
|
||||
done(zx_auth:add_package(PackageName));
|
||||
do(_) ->
|
||||
usage_exit(22).
|
||||
|
||||
@ -203,16 +245,23 @@ do(_) ->
|
||||
|
||||
done(ok) ->
|
||||
halt(0);
|
||||
done({error, Reason}) when is_atom(Reason) ->
|
||||
ok = log(error, "Operation failed with: ~tp", [Reason]),
|
||||
halt(1);
|
||||
done({error, Code}) when is_integer(Code) ->
|
||||
ok = log(error, "Operation failed with code: ~w", [Code]),
|
||||
halt(Code);
|
||||
done({error, Reason}) ->
|
||||
ok = log(error, "Operation failed with: ~160tp", [Reason]),
|
||||
halt(1);
|
||||
done({error, Info, Code}) ->
|
||||
ok = log(error, Info),
|
||||
halt(Code).
|
||||
|
||||
|
||||
-spec not_done(outcome()) -> ok | no_return().
|
||||
|
||||
not_done(ok) -> ok;
|
||||
not_done(Error) -> done(Error).
|
||||
|
||||
|
||||
-spec compatibility_check(Platforms) -> ok | no_return()
|
||||
when Platforms :: unix | win32.
|
||||
%% @private
|
||||
@ -229,7 +278,7 @@ compatibility_check(Platforms) ->
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
Message = "Unfortunately this command is not available on ~tp ~tp",
|
||||
Message = "Unfortunately this command is not available on ~tw ~tw",
|
||||
ok = log(error, Message, [Family, Name]),
|
||||
halt(0)
|
||||
end.
|
||||
@ -249,6 +298,7 @@ compatibility_check(Platforms) ->
|
||||
%% @equiv application:ensure_started(zx).
|
||||
|
||||
start() ->
|
||||
% ok = application:ensure_started(sasl),
|
||||
ok = application:ensure_started(zx),
|
||||
zx_daemon:init_connections().
|
||||
|
||||
@ -321,9 +371,9 @@ unsubscribe() ->
|
||||
|
||||
%%% Execution of application
|
||||
|
||||
-spec run(Identifier, RunArgs) -> no_return()
|
||||
when Identifier :: string(),
|
||||
RunArgs :: [string()].
|
||||
-spec run(PackageString, RunArgs) -> no_return()
|
||||
when PackageString :: string(),
|
||||
RunArgs :: [string()].
|
||||
%% @private
|
||||
%% Given a program Identifier and a list of Args, attempt to locate the program and its
|
||||
%% dependencies and run the program. This implies determining whether the program and
|
||||
@ -341,20 +391,47 @@ unsubscribe() ->
|
||||
%% If there is a problem anywhere in the locating, discovery, building, and loading
|
||||
%% procedure the runtime will halt with an error message.
|
||||
|
||||
run(Identifier, RunArgs) ->
|
||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||
FuzzyID =
|
||||
case zx_lib:package_id(Identifier) of
|
||||
{ok, Fuzzy} ->
|
||||
Fuzzy;
|
||||
{error, invalid_package_string} ->
|
||||
error_exit("Bad package string: ~ts", [Identifier], ?LINE)
|
||||
end,
|
||||
{ok, PackageID} = ensure_installed(FuzzyID),
|
||||
ok = build(PackageID),
|
||||
run(PackageString, RunArgs) ->
|
||||
case zx_lib:package_id(PackageString) of
|
||||
{ok, FuzzyID} -> run2(FuzzyID, RunArgs);
|
||||
Error -> log(info, "run/2 got ~tp", [Error]), Error
|
||||
end.
|
||||
|
||||
|
||||
run2(FuzzyID = {Realm, Name, _}, RunArgs) ->
|
||||
case resolve_installed_version(FuzzyID) of
|
||||
exact -> run3(FuzzyID, RunArgs);
|
||||
{ok, Installed} -> run3({Realm, Name, Installed}, RunArgs);
|
||||
not_found -> run3_maybe(FuzzyID, RunArgs)
|
||||
end.
|
||||
|
||||
|
||||
run3_maybe(PackageID, RunArgs) ->
|
||||
{ok, ID} = zx_daemon:latest(PackageID),
|
||||
case wait_result(ID) of
|
||||
{ok, Version} ->
|
||||
NewID = setelement(3, PackageID, Version),
|
||||
{ok, PackageString} = zx_lib:package_string(NewID),
|
||||
ok = log(info, "Fetching ~ts", [PackageString]),
|
||||
run3_maybe2(NewID, RunArgs);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
run3_maybe2(PackageID, RunArgs) ->
|
||||
case fetch(PackageID) of
|
||||
ok -> run3(PackageID, RunArgs);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
|
||||
run3(PackageID, RunArgs) ->
|
||||
Dir = zx_lib:ppath(lib, PackageID),
|
||||
{ok, Meta} = zx_lib:read_project_meta(Dir),
|
||||
prepare(PackageID, Meta, Dir, RunArgs).
|
||||
Type = maps:get(type, Meta),
|
||||
Deps = maps:get(deps, Meta),
|
||||
ok = prepare([PackageID | Deps]),
|
||||
execute(Type, PackageID, Meta, Dir, RunArgs).
|
||||
|
||||
|
||||
-spec run_local(RunArgs) -> no_return()
|
||||
@ -370,52 +447,72 @@ run(Identifier, RunArgs) ->
|
||||
|
||||
run_local(RunArgs) ->
|
||||
{ok, Meta} = zx_lib:read_project_meta(),
|
||||
PackageID = maps:get(package_id, Meta),
|
||||
ok = zx_lib:build(),
|
||||
PackageID = {_, Name, _} = maps:get(package_id, Meta),
|
||||
Type = maps:get(type, Meta),
|
||||
Deps = maps:get(deps, Meta),
|
||||
{ok, Dir} = file:get_cwd(),
|
||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||
prepare(PackageID, Meta, Dir, RunArgs).
|
||||
true = os:putenv(Name ++ "_include", filename:join(Dir, "include")),
|
||||
case prepare(Deps) of
|
||||
ok ->
|
||||
ok = file:set_cwd(Dir),
|
||||
ok = zx_lib:build(),
|
||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||
execute(Type, PackageID, Meta, Dir, RunArgs);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
-spec prepare(PackageID, Meta, Dir, RunArgs) -> no_return()
|
||||
when PackageID :: package_id(),
|
||||
Meta :: package_meta(),
|
||||
Dir :: file:filename(),
|
||||
RunArgs :: [string()].
|
||||
-spec prepare([zx:package_id()]) -> ok.
|
||||
%% @private
|
||||
%% Execution prep common to all packages.
|
||||
|
||||
prepare(PackageID, Meta, Dir, RunArgs) ->
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
ok = log(info, "Preparing ~ts...", [PackageString]),
|
||||
Type = maps:get(type, Meta),
|
||||
Deps = maps:get(deps, Meta),
|
||||
prepare(Deps) ->
|
||||
true = os:putenv("zx_include", filename:join(os:getenv("ZX_DIR"), "include")),
|
||||
ok = lists:foreach(fun include_env/1, Deps),
|
||||
NotInstalled = fun(P) -> not filelib:is_dir(zx_lib:ppath(lib, P)) end,
|
||||
Needed = lists:filter(NotInstalled, Deps),
|
||||
ok = lists:foreach(fun install/1, Needed),
|
||||
ok = lists:foreach(fun build/1, Needed),
|
||||
execute(Type, PackageID, Meta, Dir, RunArgs).
|
||||
acquire(Needed, Deps).
|
||||
|
||||
acquire([Dep | Rest], Deps) ->
|
||||
case fetch(Dep) of
|
||||
ok -> acquire(Rest, Deps);
|
||||
Error -> Error
|
||||
end;
|
||||
acquire([], Deps) ->
|
||||
make(Deps).
|
||||
|
||||
make([Dep | Rest]) ->
|
||||
case zx_daemon:build(Dep) of
|
||||
ok -> make(Rest);
|
||||
Error -> Error
|
||||
end;
|
||||
make([]) ->
|
||||
log(info, "Deps prepared.").
|
||||
|
||||
|
||||
-spec install(PackageString :: string()) -> zx:outcome().
|
||||
%% @private
|
||||
%% Installs a package from upstream.
|
||||
|
||||
install(PackageString) ->
|
||||
{ok, ID} = zx_daemon:install(PackageString),
|
||||
install(PackageString, ID).
|
||||
include_env(PackageID = {_, Name, _}) ->
|
||||
Path = filename:join(zx_lib:ppath(lib, PackageID), "include"),
|
||||
os:putenv(Name ++ "_include", Path).
|
||||
|
||||
|
||||
install(PackageString, ID) ->
|
||||
-spec fetch(zx:package_id()) -> zx:outcome().
|
||||
|
||||
fetch(PackageID) ->
|
||||
{ok, ID} = zx_daemon:fetch(PackageID),
|
||||
fetch2(ID).
|
||||
|
||||
fetch2(ID) ->
|
||||
receive
|
||||
{result, ID, done} ->
|
||||
ok;
|
||||
{result, ID, {hops, Count}} ->
|
||||
ok = log(info, "~ts ~w hops away.", [PackageString, Count]),
|
||||
install(PackageString, ID);
|
||||
ok = log(info, "Inbound; ~w hops away.", [Count]),
|
||||
fetch2(ID);
|
||||
{result, ID, {error, Reason}} ->
|
||||
{error, Reason, 1}
|
||||
after 60000 ->
|
||||
after 10000 ->
|
||||
{error, timeout, 62}
|
||||
end.
|
||||
|
||||
@ -434,11 +531,9 @@ execute(app, PackageID, Meta, Dir, RunArgs) ->
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
ok = log(info, "Starting ~ts.", [PackageString]),
|
||||
Name = element(2, PackageID),
|
||||
AppTag = list_to_atom(Name),
|
||||
{AppMod, _} = maps:get(appmod, Meta),
|
||||
ok = zx_daemon:pass_meta(Meta, Dir, RunArgs),
|
||||
AppTag = list_to_atom(Name),
|
||||
ok = ensure_all_started(AppTag),
|
||||
ok = pass_argv(AppMod, RunArgs),
|
||||
log(info, "Launcher complete.");
|
||||
execute(lib, PackageID, _, _, _) ->
|
||||
Message = "Lib ~ts is available on the system, but is not a standalone app.",
|
||||
@ -459,58 +554,7 @@ execute(lib, PackageID, _, _, _) ->
|
||||
ensure_all_started(AppMod) ->
|
||||
case application:ensure_all_started(AppMod) of
|
||||
{ok, []} -> ok;
|
||||
{ok, Apps} -> log(info, "Started ~tp", [Apps])
|
||||
end.
|
||||
|
||||
|
||||
-spec pass_argv(AppMod, Args) -> ok
|
||||
when AppMod :: module(),
|
||||
Args :: [string()].
|
||||
%% @private
|
||||
%% Check whether the AppMod:accept_argv/1 is implemented. If so, pass in the
|
||||
%% command line arguments provided.
|
||||
|
||||
pass_argv(AppMod, Args) ->
|
||||
case lists:member({accept_argv, 1}, AppMod:module_info(exports)) of
|
||||
true -> AppMod:accept_argv(Args);
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
|
||||
|
||||
|
||||
-spec ensure_installed(PackageID) -> Result | no_return()
|
||||
when PackageID :: package_id(),
|
||||
Result :: {ok, ActualID :: package_id()}.
|
||||
%% @private
|
||||
%% Given a PackageID, check whether it is installed on the system, and if not, ensure
|
||||
%% that the package is either in the cache or can be downloaded. If all attempts at
|
||||
%% locating or acquiring the package fail, then exit with an error.
|
||||
|
||||
ensure_installed(PackageID = {Realm, Name, Version}) ->
|
||||
case resolve_installed_version(PackageID) of
|
||||
exact -> {ok, PackageID};
|
||||
{ok, Installed} -> {ok, {Realm, Name, Installed}};
|
||||
not_found -> fetch_one({Realm, Name, Version})
|
||||
end.
|
||||
|
||||
|
||||
-spec fetch_one(PackageID) -> {ok, ActualID} | no_return()
|
||||
when PackageID :: package_id(),
|
||||
ActualID :: package_id().
|
||||
%% @private
|
||||
%% A helper function to deal with the special case of downloading and installing a
|
||||
%% single primary application package with a possibly incomplete version designator.
|
||||
%% All other fetches are for arbitrarily long lists of package IDs with complete
|
||||
%% version numbers (dependency fetches).
|
||||
|
||||
fetch_one(PackageID) ->
|
||||
case zx_daemon:fetch([PackageID]) of
|
||||
{{ok, [ActualID]}, {error, []}} ->
|
||||
ok = install(ActualID),
|
||||
{ok, ActualID};
|
||||
{{ok, []}, {error, [{PackageID, Reason}]}} ->
|
||||
error_exit("Package fetch failed with: ~tp", [Reason], ?LINE)
|
||||
{ok, Apps} -> log(info, "Started ~160tp", [Apps])
|
||||
end.
|
||||
|
||||
|
||||
@ -546,26 +590,11 @@ tuplize(String, Acc) ->
|
||||
end.
|
||||
|
||||
|
||||
-spec build(package_id()) -> ok.
|
||||
%% @private
|
||||
%% Given an AppID, build the project from source and add it to the current lib path.
|
||||
|
||||
build(PackageID) ->
|
||||
{ok, CWD} = file:get_cwd(),
|
||||
ok = file:set_cwd(zx_lib:ppath(lib, PackageID)),
|
||||
ok = zx_lib:build(),
|
||||
file:set_cwd(CWD).
|
||||
|
||||
|
||||
%% FIXME
|
||||
%-spec ensure_package_dirs(package_id()) -> ok.
|
||||
%%% @private
|
||||
%%% Procedure to guarantee that directory locations necessary for the indicated app to
|
||||
%%% run have been created or halt execution.
|
||||
%
|
||||
%ensure_package_dirs(PackageID) ->
|
||||
% Dirs = [zx_lib:ppath(D, PackageID) || D <- [etc, var, tmp, log, lib]],
|
||||
% lists:foreach(fun zx_lib:force_dir/1, Dirs).
|
||||
wait_result(ID) ->
|
||||
receive
|
||||
{result, ID, Result} -> Result
|
||||
after 5000 -> {error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
|
||||
@ -578,100 +607,105 @@ build(PackageID) ->
|
||||
%% with the provided exit code.
|
||||
|
||||
usage_exit(Code) ->
|
||||
ok = usage(),
|
||||
ok = lists:foreach(fun io:format/1, usage_all()),
|
||||
halt(Code).
|
||||
|
||||
|
||||
-spec usage() -> ok.
|
||||
%% @private
|
||||
%% Display the zx command line usage message.
|
||||
help(top) -> show_help();
|
||||
help(user) -> show_help([usage_header(), usage_user(), usage_spec()]);
|
||||
help(dev) -> show_help([usage_header(), usage_dev(), usage_spec()]);
|
||||
help(sysop) -> show_help([usage_header(), usage_sysop(), usage_spec()]).
|
||||
|
||||
usage() ->
|
||||
|
||||
show_help() ->
|
||||
T =
|
||||
"ZX usage: zx [command] [object] [args]~n"
|
||||
"~n"
|
||||
"User Actions:~n"
|
||||
" zx help~n"
|
||||
" zx run PackageID [Args]~n"
|
||||
" zx runlocal [Args]~n"
|
||||
" zx list realms~n"
|
||||
" zx list packages Realm~n"
|
||||
" zx list versions PackageID~n"
|
||||
" zx latest PackageID~n"
|
||||
" zx import realm RealmFile~n"
|
||||
" zx drop realm Realm~n"
|
||||
" zx install PackageID~n"
|
||||
" zx logpath [Package [1-10]]~n"
|
||||
" zx status~n"
|
||||
" zx set timeout Value~n"
|
||||
" zx add mirror Realm Host:Port~n"
|
||||
" zx drop mirror Realm Host:Port~n"
|
||||
"~n"
|
||||
"Developer/Packager/Maintainer Actions:~n"
|
||||
" zx create project [app | lib] PackageID~n"
|
||||
" zx init Type PackageID~n"
|
||||
" zx list deps [PackageID]~n"
|
||||
" zx set dep PackageID~n"
|
||||
" zx drop dep PackageID~n"
|
||||
" zx verup Level~n"
|
||||
" zx set version Version~n"
|
||||
" zx update .app~n"
|
||||
" zx create plt~n"
|
||||
" zx dialyze~n"
|
||||
" zx package Path~n"
|
||||
" zx submit ZspFile~n"
|
||||
" zx list pending PackageName~n"
|
||||
" zx list resigns Realm~n"
|
||||
" zx list packagers PackageName~n"
|
||||
" zx list maintainers PackageName~n"
|
||||
" zx list sysops Realm~n"
|
||||
" zx review PackageID~n"
|
||||
" zx approve PackageID~n"
|
||||
" zx reject PackageID~n"
|
||||
" zx add key Realm KeyName~n"
|
||||
" zx get key Realm KeyName~n"
|
||||
" zx create user~n"
|
||||
" zx create userfiles Realm UserName~n"
|
||||
" zx create keypair~n"
|
||||
" zx export user UserID~n"
|
||||
" zx import user ZdufFile~n"
|
||||
"~n"
|
||||
"Sysop Actions:~n"
|
||||
" zx add user ZpufFile~n"
|
||||
" zx add package PackageName~n"
|
||||
" zx add packager PackageName UserID~n"
|
||||
" zx add maintainer PackageName UserID~n"
|
||||
" zx add sysop UserID~n"
|
||||
" zx accept PackageID~n"
|
||||
" zx create realm~n"
|
||||
" zx create realmfile Realm~n"
|
||||
" zx takeover Realm~n"
|
||||
" zx abdicate Realm~n"
|
||||
"~n"
|
||||
"Where~n"
|
||||
" PackageID :: A string of the form Realm-Name[-Version]~n"
|
||||
" Args :: Arguments to pass to the application~n"
|
||||
" Type :: The project type: a standalone \"app\" or a \"lib\"~n"
|
||||
" Version :: Version string X, X.Y, or X.Y.Z: \"1\", \"1.2\", \"1.2.3\"~n"
|
||||
" RealmFile :: Path to a valid .zrf realm file~n"
|
||||
" Realm :: The name of a realm as a string [:a-z:]~n"
|
||||
" KeyName :: The prefix of a keypair to drop~n"
|
||||
" Level :: The version level, one of \"major\", \"minor\", or \"patch\"~n"
|
||||
" Path :: Path to a valid project directory or .zsp file~n",
|
||||
"ZX help has three forms, one for each category of commands:~n"
|
||||
" zx help [user | dev | sysop]~n"
|
||||
"The user manual is also available online at: http://zxq9.com/projects/zomp/~n",
|
||||
io:format(T).
|
||||
|
||||
|
||||
show_help(Info) -> lists:foreach(fun io:format/1, Info).
|
||||
|
||||
%%% Error exits
|
||||
|
||||
-spec error_exit(Format, Args, Line) -> no_return()
|
||||
when Format :: string(),
|
||||
Args :: [term()],
|
||||
Line :: non_neg_integer().
|
||||
%% @private
|
||||
%% Format an error message in a way that makes it easy to locate.
|
||||
usage_all() ->
|
||||
[usage_header(), usage_user(), usage_dev(), usage_sysop(), usage_spec()].
|
||||
|
||||
error_exit(Format, Args, Line) ->
|
||||
File = filename:basename(?FILE),
|
||||
ok = log(error, "~ts:~tp: " ++ Format, [File, Line | Args]),
|
||||
halt(1).
|
||||
usage_header() ->
|
||||
"ZX usage: zx [command] [object] [args]~n~n".
|
||||
|
||||
usage_user() ->
|
||||
"User Actions:~n"
|
||||
" zx help~n"
|
||||
" zx run PackageID [Args]~n"
|
||||
" zx list realms~n"
|
||||
" zx list packages Realm~n"
|
||||
" zx list versions PackageID~n"
|
||||
" zx latest PackageID~n"
|
||||
" zx import realm RealmFile~n"
|
||||
" zx drop realm Realm~n"
|
||||
" zx logpath [Package [1-10]]~n"
|
||||
" zx status~n"
|
||||
" zx set timeout Value~n"
|
||||
" zx add mirror Realm Host:Port~n"
|
||||
" zx drop mirror Realm Host:Port~n~n".
|
||||
|
||||
usage_dev() ->
|
||||
"Developer/Packager/Maintainer Actions:~n"
|
||||
" zx create project~n"
|
||||
" zx runlocal [Args]~n"
|
||||
" zx init~n"
|
||||
" zx list deps [PackageID]~n"
|
||||
" zx set dep PackageID~n"
|
||||
" zx drop dep PackageID~n"
|
||||
" zx verup Level~n"
|
||||
" zx set version Version~n"
|
||||
" zx update .app~n"
|
||||
" zx create plt~n"
|
||||
" zx dialyze~n"
|
||||
" zx package Path~n"
|
||||
" zx submit ZSP~n"
|
||||
" zx list pending PackageName~n"
|
||||
" zx review PackageID~n"
|
||||
" zx approve PackageID~n"
|
||||
" zx reject PackageID~n"
|
||||
" zx add key~n"
|
||||
" zx create user~n"
|
||||
" zx create userfile~n"
|
||||
" zx create keypair~n"
|
||||
" zx export user~n"
|
||||
" zx import user [ZPUF | ZDUF]~n"
|
||||
" zx list packagers PackageName~n"
|
||||
" zx list maintainers PackageName~n"
|
||||
" zx list sysops Realm~n"
|
||||
" zx install ZSP~n~n".
|
||||
|
||||
usage_sysop() ->
|
||||
"Sysop Actions:~n"
|
||||
" zx list approved Realm~n"
|
||||
" zx accept PackageID~n"
|
||||
" zx add package PackageName~n"
|
||||
" zx list users Realm~n"
|
||||
" zx add user ZPUF~n"
|
||||
" zx add packager PackageName UserID~n"
|
||||
" zx add maintainer PackageName UserID~n"
|
||||
" zx add sysop UserID~n"
|
||||
" zx create realm~n"
|
||||
" zx create realmfile~n"
|
||||
" zx takeover Realm~n"
|
||||
" zx abdicate Realm~n~n".
|
||||
|
||||
usage_spec() ->
|
||||
"Where~n"
|
||||
" PackageID :: A string of the form Realm-Name[-Version]~n"
|
||||
" Args :: Arguments to pass to the application~n"
|
||||
" Type :: The project type: a standalone \"app\" or a \"lib\"~n"
|
||||
" Version :: Version string X, X.Y, or X.Y.Z: \"1\", \"1.2\", \"1.2.3\"~n"
|
||||
" RealmFile :: Path to a valid .zrf realm file~n"
|
||||
" Realm :: The name of a realm as a string [:a-z:]~n"
|
||||
" KeyName :: The prefix of a keypair to drop~n"
|
||||
" Level :: The version level, one of \"major\", \"minor\", or \"patch\"~n"
|
||||
" Path :: Path or filename.~n"
|
||||
" ZSP :: Path to a .zsp file (Zomp Source Package).~n"
|
||||
" ZPUF :: Path to a .zpuf file (Zomp Public User File).~n"
|
||||
" ZDUF :: Path to a .zduf file (Zomp DANGEROUS User File).~n".
|
||||
|
||||
@ -13,9 +13,13 @@
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([list_pending/1, list_resigns/1,
|
||||
-export([list_pending/1, list_approved/1,
|
||||
submit/1, review/1, approve/1, reject/1, accept/1,
|
||||
add_packager/2, add_maintainer/2, add_sysop/1,
|
||||
list_users/1, list_packagers/1, list_maintainers/1, list_sysops/1,
|
||||
add_user/1,
|
||||
add_packager/2, rem_packager/2,
|
||||
add_maintainer/2, rem_maintainer/2,
|
||||
add_sysop/1,
|
||||
add_package/1]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
@ -25,453 +29,570 @@
|
||||
%%% Functions
|
||||
|
||||
|
||||
-spec list_pending(PackageName :: string()) -> no_return().
|
||||
-spec list_pending(PackageName :: string()) -> zx:outcome().
|
||||
%% @private
|
||||
%% List the versions of a package that are pending review. The package name is input by
|
||||
%% the user as a string of the form "otpr-zomp" and the output is a list of full
|
||||
%% package IDs, printed one per line to stdout (like "otpr-zomp-3.2.2").
|
||||
|
||||
list_pending(PackageName) ->
|
||||
Package = {Realm, Name} =
|
||||
case zx_lib:package_id(PackageName) of
|
||||
{ok, {R, N, {z, z, z}}} ->
|
||||
{R, N};
|
||||
{error, invalid_package_string} ->
|
||||
error_exit("~tp is not a valid package name.", [PackageName], ?LINE)
|
||||
end,
|
||||
case zx_daemon:list_pending(Package) of
|
||||
{ok, []} ->
|
||||
Message = "Package ~ts has no versions pending.",
|
||||
ok = log(info, Message, [PackageName]),
|
||||
halt(0);
|
||||
{ok, Versions} ->
|
||||
Print =
|
||||
fun(Version) ->
|
||||
{ok, PackageString} = zx_lib:package_string({Realm, Name, Version}),
|
||||
io:format("~ts~n", [PackageString])
|
||||
end,
|
||||
ok = lists:foreach(Print, Versions),
|
||||
halt(0);
|
||||
{error, bad_realm} ->
|
||||
error_exit("Bad realm name.", ?LINE);
|
||||
{error, bad_package} ->
|
||||
error_exit("Bad package name.", ?LINE);
|
||||
{error, network} ->
|
||||
Message = "Network issues are preventing connection to the realm.",
|
||||
error_exit(Message, ?LINE)
|
||||
case zx_lib:package_id(PackageName) of
|
||||
{ok, {Realm, Name, {z, z, z}}} -> list_pending2(Realm, Name);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
list_pending2(Realm, Name) ->
|
||||
case connect(Realm) of
|
||||
{ok, Socket} -> list_pending3(Realm, Name, Socket);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
-spec list_resigns(zx:realm()) -> no_return().
|
||||
list_pending3(Realm, Name, Socket) ->
|
||||
Message = <<"ZOMP AUTH 1:", 0:24, 5:8, (term_to_binary({Realm, Name}))/binary>>,
|
||||
ok = gen_tcp:send(Socket, Message),
|
||||
receive
|
||||
{tcp, Socket, <<0:8, Bin/binary>>} -> list_pending4(Realm, Name, Socket, Bin);
|
||||
{tcp, Socket, Error} -> done(Socket, Error);
|
||||
{tcp_closed, Socket} -> {error, "Socket closed unexpectedly."}
|
||||
after 5000 -> done(Socket, timeout)
|
||||
end.
|
||||
|
||||
list_pending4(Realm, Name, Socket, Bin) ->
|
||||
ok = zx_net:disconnect(Socket),
|
||||
case zx_lib:b_to_ts(Bin) of
|
||||
{ok, Versions} -> list_pending5(Realm, Name, Versions);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
list_pending5(Realm, Name, Versions) ->
|
||||
Print =
|
||||
fun(Version) ->
|
||||
PackageID = {Realm, Name, Version},
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
io:format("~ts~n", [PackageString])
|
||||
end,
|
||||
lists:foreach(Print, Versions).
|
||||
|
||||
|
||||
-spec list_approved(zx:realm()) -> zx:outcome().
|
||||
%% @private
|
||||
%% List the package ids of all packages waiting in the resign queue for the given realm,
|
||||
%% printed to stdout one per line.
|
||||
|
||||
list_resigns(Realm) ->
|
||||
case zx_daemon:list_resigns(Realm) of
|
||||
{ok, []} ->
|
||||
Message = "No packages pending signature in ~tp.",
|
||||
ok = log(info, Message, [Realm]),
|
||||
halt(0);
|
||||
{ok, PackageIDs} ->
|
||||
Print =
|
||||
fun(PackageID) ->
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
io:format("~ts~n", [PackageString])
|
||||
end,
|
||||
ok = lists:foreach(Print, PackageIDs),
|
||||
halt(0);
|
||||
{error, bad_realm} ->
|
||||
error_exit("Bad realm name.", ?LINE);
|
||||
{error, no_realm} ->
|
||||
error_exit("Realm \"~ts\" is not configured.", ?LINE);
|
||||
{error, network} ->
|
||||
Message = "Network issues are preventing connection to the realm.",
|
||||
error_exit(Message, ?LINE)
|
||||
list_approved(Realm) ->
|
||||
case connect(Realm) of
|
||||
{ok, Socket} -> list_approved2(Realm, Socket);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
list_approved2(Realm, Socket) ->
|
||||
Message = <<"ZOMP AUTH 1:", 0:24, 7:8, (term_to_binary(Realm))/binary>>,
|
||||
ok = gen_tcp:send(Socket, Message),
|
||||
receive
|
||||
{tcp, Socket, <<0:8, Bin/binary>>} -> list_approved3(Socket, Bin, Realm);
|
||||
{tcp, Socket, Error} -> done(Socket, Error);
|
||||
{tcp_closed, Socket} -> {error, "Socket closed unexpectedly."}
|
||||
after 5000 -> done(Socket, timeout)
|
||||
end.
|
||||
|
||||
list_approved3(Socket, Bin, Realm) ->
|
||||
ok = zx_net:disconnect(Socket),
|
||||
case zx_lib:b_to_ts(Bin) of
|
||||
{ok, PackageIDs} -> list_approved4(Realm, PackageIDs);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
list_approved4(Realm, PackageIDs) ->
|
||||
Print =
|
||||
fun({Name, Version}) ->
|
||||
{ok, PackageString} = zx_lib:package_string({Realm, Name, Version}),
|
||||
io:format("~ts~n", [PackageString])
|
||||
end,
|
||||
lists:foreach(Print, PackageIDs).
|
||||
|
||||
|
||||
-spec submit(ZspPath :: file:filename()) -> zx:outcome().
|
||||
%% @private
|
||||
%% Submit a package to the appropriate "prime" server for the given realm.
|
||||
|
||||
submit(ZspPath) ->
|
||||
{ok, ZspBin} = file:read_file(ZspPath),
|
||||
<<SigSize:24, Sig:SigSize/binary, Signed/binary>> = ZspBin,
|
||||
<<MetaSize:16, MetaBin:MetaSize/binary, _/binary>> = Signed,
|
||||
{ok, {PackageID, SigKeyName, _}} = zx_lib:b_to_ts(MetaBin),
|
||||
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
|
||||
true = zx_key:verify(Signed, Sig, PubKey),
|
||||
{ok, Socket} = connect_auth(element(1, PackageID)),
|
||||
ok = send(Socket, {submit, PackageID}),
|
||||
ok = recv_or_die(Socket),
|
||||
ok = gen_tcp:send(Socket, ZspBin),
|
||||
ok = log(info, "Done sending contents of ~tp", [ZspPath]),
|
||||
Outcome = recv_or_die(Socket),
|
||||
log(info, "Response: ~tp", [Outcome]),
|
||||
disconnect(Socket).
|
||||
case file:read_file(ZspPath) of
|
||||
{ok, ZspBin} -> submit2(ZspBin);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
submit2(ZspBin = <<SigSize:24, Sig:SigSize/binary, Signed/binary>>) ->
|
||||
<<MetaSize:16, MetaBin:MetaSize/binary, _/binary>> = Signed,
|
||||
{ok, {PackageID = {Realm, _, _}, KeyName, _, _}} = zx_lib:b_to_ts(MetaBin),
|
||||
UserName = zx_local:select_user(Realm),
|
||||
KeyID = {Realm, KeyName},
|
||||
case zx_key:ensure_keypair(KeyID) of
|
||||
true ->
|
||||
{ok, PKey} = zx_key:load(public, KeyID),
|
||||
{ok, DKey} = zx_key:load(private, KeyID),
|
||||
case zx_key:verify(Signed, Sig, PKey) of
|
||||
true -> submit3(PackageID, ZspBin, UserName, KeyName, DKey);
|
||||
false -> {error, bad_sig}
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
submit3({Realm, Name, Ver}, ZspBin, UserName, KeyName, Key) ->
|
||||
Payload = {Realm, UserName, KeyName, {Name, Ver}},
|
||||
case connect(Realm) of
|
||||
{ok, Socket} -> submit4(Socket, Payload, ZspBin, Key);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
submit4(Socket, Payload, ZspBin, Key) ->
|
||||
Command = 1,
|
||||
Request = pack_and_sign(Command, Payload, Key),
|
||||
ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>),
|
||||
case zx_net:tx(Socket, ZspBin) of
|
||||
ok -> done(Socket);
|
||||
Error -> done(Socket, Error)
|
||||
end.
|
||||
|
||||
|
||||
-spec review(PackageString :: string()) -> zx:outcome().
|
||||
|
||||
review(PackageString) ->
|
||||
PackageID = {Realm, _, _} = zx_lib:package_id(PackageString),
|
||||
Socket = connect_auth_or_die(Realm),
|
||||
ok = send(Socket, {review, PackageID}),
|
||||
{ok, ZrpBin} = recv_or_die(Socket),
|
||||
ok = send(Socket, ok),
|
||||
ok = disconnect(Socket),
|
||||
{ok, Files} = erl_tar:extract({binary, ZrpBin}, [memory]),
|
||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
||||
Meta = binary_to_term(MetaBin, [safe]),
|
||||
PackageID = maps:get(package_id, Meta),
|
||||
{KeyID, Signature} = maps:get(sig, Meta),
|
||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
||||
TgzFile = PackageString ++ ".tgz",
|
||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
||||
ok = zx_key:verify(TgzData, Signature, PubKey),
|
||||
ok =
|
||||
case file:make_dir(PackageString) of
|
||||
ok ->
|
||||
log(info, "Will unpack to directory ./~ts", [PackageString]);
|
||||
{error, Error} ->
|
||||
Message = "Creating dir ./~ts failed with ~ts. Aborting.",
|
||||
ok = log(error, Message, [PackageString, Error]),
|
||||
halt(1)
|
||||
end,
|
||||
ok = erl_tar:extract({binary, TgzData}, [compressed, {cwd, PackageString}]),
|
||||
ok = log(info, "Unpacked and awaiting inspection."),
|
||||
halt(0).
|
||||
case zx_lib:package_id(PackageString) of
|
||||
{ok, PackageID} -> review2(PackageID);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
review2(PackageID = {Realm, _, _}) ->
|
||||
case connect(Realm) of
|
||||
{ok, Socket} -> review3(PackageID, Socket);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
review3(PackageID, Socket) ->
|
||||
Command = 8,
|
||||
TermBin = term_to_binary(PackageID),
|
||||
Request = <<0:24, Command:8, TermBin/binary>>,
|
||||
ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>),
|
||||
receive
|
||||
{tcp, Socket, <<0:1, 0:7>>} -> review4(PackageID, Socket);
|
||||
{tcp, Socket, Bin} -> done(Socket, Bin);
|
||||
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||
after 5000 -> done(Socket, timeout)
|
||||
end.
|
||||
|
||||
review4(PackageID, Socket) ->
|
||||
case zx_net:rx(Socket) of
|
||||
{ok, <<Size:24, Sig:Size/binary, Signed/binary>>} ->
|
||||
ok = zx_net:disconnect(Socket),
|
||||
review5(PackageID, Sig, Signed);
|
||||
Error ->
|
||||
done(Socket, Error)
|
||||
end.
|
||||
|
||||
review5(PackageID, Sig, Signed = <<Size:16, MetaBin:Size/binary, TgzBin/binary>>) ->
|
||||
case zx_lib:b_to_ts(MetaBin) of
|
||||
{ok, {PackageID = {Realm, _, _}, KeyName, _, _}} ->
|
||||
review6(PackageID, {Realm, KeyName}, Signed, Sig, TgzBin);
|
||||
{ok, {UnexpectedID, _, _, _}} ->
|
||||
{ok, Requested} = zx_lib:package_string(PackageID),
|
||||
{ok, Unexpected} = zx_lib:package_string(UnexpectedID),
|
||||
Message = "Requested ~ts, but inside was ~ts! Aborting.",
|
||||
ok = log(warning, Message, [Requested, Unexpected]),
|
||||
{error, "Wrong package received.", 29};
|
||||
error ->
|
||||
{error, bad_response}
|
||||
end.
|
||||
|
||||
review6(PackageID, KeyID, Signed, Sig, TgzBin) ->
|
||||
case zx_key:load(public, KeyID) of
|
||||
{ok, Key} -> review7(PackageID, Key, Signed, Sig, TgzBin);
|
||||
Error -> Error % TODO: Fetch unknown keys
|
||||
end.
|
||||
|
||||
|
||||
approve(PackageID = {Realm, _, _}) ->
|
||||
Socket = connect_auth_or_die(Realm),
|
||||
ok = send(Socket, {approve, PackageID}),
|
||||
ok = recv_or_die(Socket),
|
||||
ok = log(info, "ok"),
|
||||
halt(0).
|
||||
review7(PackageID, SigKey, Signed, Sig, TgzBin) ->
|
||||
case zx_key:verify(Signed, Sig, SigKey) of
|
||||
true -> review8(PackageID, TgzBin);
|
||||
false -> {error, bad_sig}
|
||||
end.
|
||||
|
||||
review8(PackageID, TgzBin) ->
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
case file:make_dir(PackageString) of
|
||||
ok ->
|
||||
review9(PackageString, TgzBin);
|
||||
{error, Error} ->
|
||||
Message = "Creating dir ./~ts failed with ~ts. Aborting.",
|
||||
ok = log(error, Message, [PackageString, Error]),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
review9(PackageString, TgzBin) ->
|
||||
ok = erl_tar:extract({binary, TgzBin}, [compressed, {cwd, PackageString}]),
|
||||
log(info, "Sources unpacked to ./~ts", [PackageString]).
|
||||
|
||||
|
||||
reject(PackageID = {Realm, _, _}) ->
|
||||
Socket = connect_auth_or_die(Realm),
|
||||
ok = send(Socket, {reject, PackageID}),
|
||||
ok = recv_or_die(Socket),
|
||||
ok = log(info, "ok"),
|
||||
halt(0).
|
||||
approve(Package) -> package_operation(9, Package).
|
||||
|
||||
reject(Package) -> package_operation(10, Package).
|
||||
|
||||
|
||||
-spec package_operation(Code :: 9 | 10, Package :: string()) -> zx:outcome().
|
||||
|
||||
package_operation(Code, Package) ->
|
||||
case zx_lib:package_id(Package) of
|
||||
{ok, {Realm, Name, Version}} -> make_su_request(Code, Realm, {Name, Version});
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
|
||||
-spec accept(PackageString :: string()) -> zx:outcome().
|
||||
|
||||
accept(PackageString) ->
|
||||
PackageID = {Realm, _, _} = zx_lib:package_id(PackageString),
|
||||
RealmConf = zx_lib:load_realm_conf(Realm),
|
||||
{package_keys, PackageKeys} = lists:keyfind(package_keys, 1, RealmConf),
|
||||
KeySelection = [{K, {R, K}} || {R, K} <- [element(1, K) || K <- PackageKeys]],
|
||||
PackageKeyID = zx_tty:select(KeySelection),
|
||||
{ok, PackageKey} = zx_key:load(private, PackageKeyID),
|
||||
Socket = connect_auth_or_die(Realm),
|
||||
ok = send(Socket, {accept, PackageID}),
|
||||
{ok, ZrpBin} = recv_or_die(Socket),
|
||||
{ok, Files} = erl_tar:extract({binary, ZrpBin}, [memory]),
|
||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
||||
Meta = binary_to_term(MetaBin, [safe]),
|
||||
PackageID = maps:get(package_id, Meta),
|
||||
{KeyID, Signature} = maps:get(sig, Meta),
|
||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
||||
TgzFile = PackageString ++ ".tgz",
|
||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
||||
ok = zx_key:verify(TgzData, Signature, PubKey),
|
||||
ReSignature = public_key:sign(TgzData, sha512, PackageKey),
|
||||
FinalMeta = maps:put(sig, {PackageKeyID, ReSignature}, Meta),
|
||||
NewMetaBin = term_to_binary(FinalMeta),
|
||||
NewFiles = lists:keystore("zomp.meta", 1, Files, {"zomp.meta", NewMetaBin}),
|
||||
ResignedZrp = PackageString ++ ".zsp.resign",
|
||||
ok = erl_tar:create(ResignedZrp, NewFiles),
|
||||
{ok, ResignedBin} = file:read_file(ResignedZrp),
|
||||
ok = gen_tcp:send(Socket, ResignedBin),
|
||||
ok = recv_or_die(Socket),
|
||||
ok = file:delete(ResignedZrp),
|
||||
ok = recv_or_die(Socket),
|
||||
ok = disconnect(Socket),
|
||||
ok = log(info, "Resigned ~ts", [PackageString]),
|
||||
halt(0).
|
||||
case zx_lib:package_id(PackageString) of
|
||||
{ok, PackageID} -> accept2(PackageID);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
accept2(PackageID = {Realm, Name, Version}) ->
|
||||
case connect_auth(Realm) of
|
||||
{ok, AuthConn} -> accept3(PackageID, {Name, Version}, AuthConn);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
accept3(PackageID, PV, {UserName, KeyName, Key, Tag, Socket}) ->
|
||||
Command = 11,
|
||||
Payload = {Tag, UserName, KeyName, PV},
|
||||
Request = pack_and_sign(Command, Payload, Key),
|
||||
ok = gen_tcp:send(Socket, <<0:8, Request/binary>>),
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<0:1, 0:7>>} -> accept4(PackageID, KeyName, Key, Socket);
|
||||
{tcp, Socket, Bin} -> done(Socket, Bin);
|
||||
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||
after 5000 -> done(Socket, timeout)
|
||||
end.
|
||||
|
||||
accept4(PackageID, KeyName, Key, Socket) ->
|
||||
case zx_net:rx(Socket) of
|
||||
{ok, <<Size:24, Sig:Size/binary, Signed/binary>>} ->
|
||||
accept5(PackageID, {KeyName, Key, Socket}, Sig, Signed);
|
||||
Error ->
|
||||
done(Socket, Error)
|
||||
end.
|
||||
|
||||
accept5(PackageID,
|
||||
Auth,
|
||||
Sig,
|
||||
Signed = <<Size:16, MetaBin:Size/binary, TgzBin/binary>>) ->
|
||||
case zx_lib:b_to_ts(MetaBin) of
|
||||
{ok, Meta = {PackageID = {Realm, _, _}, SigKeyName, _, _}} ->
|
||||
accept6(Meta, Auth, {Realm, SigKeyName}, Signed, Sig, TgzBin);
|
||||
{ok, {UnexpectedID, _, _, _}} ->
|
||||
{ok, Requested} = zx_lib:package_string(PackageID),
|
||||
{ok, Unexpected} = zx_lib:package_string(UnexpectedID),
|
||||
Message = "Requested ~ts, but inside was ~ts! Aborting.",
|
||||
ok = log(warning, Message, [Requested, Unexpected]),
|
||||
{error, "Wrong package received.", 29};
|
||||
error ->
|
||||
{error, bad_response}
|
||||
end.
|
||||
|
||||
accept6(Meta, Auth, SigKeyID, Signed, Sig, TgzBin) ->
|
||||
case zx_key:load(public, SigKeyID) of
|
||||
{ok, SigKey} -> accept7(Meta, Auth, SigKey, Signed, Sig, TgzBin);
|
||||
Error -> Error % TODO: Fetch unknown keys
|
||||
end.
|
||||
|
||||
accept7(Meta, Auth, SigKey, Signed, Sig, TgzBin) ->
|
||||
case zx_key:verify(Signed, Sig, SigKey) of
|
||||
true -> accept8(Meta, Auth, TgzBin);
|
||||
false -> {error, bad_sig}
|
||||
end.
|
||||
|
||||
accept8({PackageID, _, Deps, Modules}, {KeyName, Key, Socket}, TgzBin) ->
|
||||
MetaBin = term_to_binary({PackageID, KeyName, Deps, Modules}),
|
||||
MetaSize = byte_size(MetaBin),
|
||||
SignMe = <<MetaSize:16, MetaBin:MetaSize/binary, TgzBin/binary>>,
|
||||
Sig = public_key:sign(SignMe, sha512, Key),
|
||||
SigSize = byte_size(Sig),
|
||||
ZspData = <<SigSize:24, Sig:SigSize/binary, SignMe/binary>>,
|
||||
ok = zx_net:tx(Socket, ZspData),
|
||||
done(Socket).
|
||||
|
||||
|
||||
add_packager(Package, UserFile) ->
|
||||
ok = log(info, "Would add ~ts to packagers for ~160tp now.", [UserFile, Package]),
|
||||
halt(0).
|
||||
list_users(Realm) ->
|
||||
list_users(2, Realm).
|
||||
|
||||
list_packagers(Package) ->
|
||||
{ok, {Realm, Name, {z, z, z}}} = zx_lib:package_id(Package),
|
||||
list_users(3, {Realm, Name}).
|
||||
|
||||
list_maintainers(Package) ->
|
||||
{ok, {Realm, Name, {z, z, z}}} = zx_lib:package_id(Package),
|
||||
list_users(4, {Realm, Name}).
|
||||
|
||||
list_sysops(Realm) ->
|
||||
list_users(5, Realm).
|
||||
|
||||
|
||||
add_maintainer(Package, UserFile) ->
|
||||
ok = log(info, "Would add ~ts to maintainer for ~160tp now.", [UserFile, Package]),
|
||||
halt(0).
|
||||
-spec list_users(Command, Target) -> zx:outcome()
|
||||
when Command :: 2..5,
|
||||
Target :: zx:realm() | zx:package().
|
||||
|
||||
list_users(Command, Target) ->
|
||||
case make_uu_request(Command, Target) of
|
||||
{ok, Users} -> lists:foreach(fun print_user/1, Users);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
print_user({UserName, RealName, [{"email", Email}]}) ->
|
||||
io:format("~ts (~ts <~ts>) ~n", [UserName, RealName, Email]).
|
||||
|
||||
|
||||
-spec add_user(file:filename()) -> zx:outcome().
|
||||
|
||||
add_user(ZPUF) ->
|
||||
case file:read_file(ZPUF) of
|
||||
{ok, Bin} -> add_user2(Bin);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
add_user2(Bin) ->
|
||||
case zx_lib:b_to_t(Bin) of
|
||||
{ok, {UserInfo, KeyData}} ->
|
||||
Realm = proplists:get_value(realm, UserInfo),
|
||||
UserData = {proplists:get_value(username, UserInfo),
|
||||
proplists:get_value(realname, UserInfo),
|
||||
proplists:get_value(contact_info, UserInfo),
|
||||
[setelement(2, KD, none) || KD <- KeyData]},
|
||||
Command = 13,
|
||||
make_su_request(Command, Realm, UserData);
|
||||
error ->
|
||||
{error, "Bad user file.", 1}
|
||||
end.
|
||||
|
||||
|
||||
add_packager(Package, User) -> user_auth_operation(15, Package, User).
|
||||
|
||||
rem_packager(Package, User) -> user_auth_operation(16, Package, User).
|
||||
|
||||
add_maintainer(Package, User) -> user_auth_operation(17, Package, User).
|
||||
|
||||
rem_maintainer(Package, User) -> user_auth_operation(18, Package, User).
|
||||
|
||||
|
||||
-spec user_auth_operation(Code, Package, User)-> zx:outcome()
|
||||
when Code :: 15..18,
|
||||
Package :: string(),
|
||||
User :: zx:user_name().
|
||||
|
||||
user_auth_operation(Code, Package, User) ->
|
||||
case zx_lib:package_id(Package) of
|
||||
{ok, {Realm, Name, {z, z, z}}} -> make_su_request(Code, Realm, {Name, User});
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
|
||||
-spec add_sysop(file:filename()) -> zx:outcome().
|
||||
|
||||
add_sysop(UserFile) ->
|
||||
ok = log(info, "Would add ~ts to sysop list.", [UserFile]),
|
||||
halt(0).
|
||||
{error, "Not yet implemented", 1}.
|
||||
|
||||
|
||||
-spec add_package(PackageName) -> no_return()
|
||||
when PackageName :: zx:package().
|
||||
%% @private
|
||||
%% A sysop command that adds a package to a realm operated by the caller.
|
||||
-spec add_package(zx:package()) -> zx:outcome().
|
||||
|
||||
add_package(PackageName) ->
|
||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||
case zx_lib:package_id(PackageName) of
|
||||
{ok, {Realm, Name, {z, z, z}}} ->
|
||||
add_package(Realm, Name);
|
||||
_ ->
|
||||
error_exit("~tp is not a valid package name.", [PackageName], ?LINE)
|
||||
{ok, {Realm, Name, {z, z, z}}} -> add_package2(Realm, Name);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
|
||||
-spec add_package(Realm, Name) -> no_return()
|
||||
when Realm :: zx:realm(),
|
||||
Name :: zx:name().
|
||||
|
||||
add_package(Realm, Name) ->
|
||||
Socket = connect_auth_or_die(Realm),
|
||||
ok = send(Socket, {add_package, {Realm, Name}}),
|
||||
ok = recv_or_die(Socket),
|
||||
ok = log(info, "\"~ts-~ts\" added successfully.", [Realm, Name]),
|
||||
halt(0).
|
||||
|
||||
|
||||
|
||||
%%% Authenticated communication with prime
|
||||
|
||||
-spec send(Socket, Message) -> ok
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Message :: term().
|
||||
%% @private
|
||||
%% Wrapper for the procedure necessary to send an internal message over the wire.
|
||||
|
||||
send(Socket, Message) ->
|
||||
Bin = term_to_binary(Message),
|
||||
gen_tcp:send(Socket, Bin).
|
||||
|
||||
|
||||
-spec recv_or_die(Socket) -> Result | no_return()
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Result :: ok | {ok, term()}.
|
||||
|
||||
recv_or_die(Socket) ->
|
||||
receive
|
||||
{tcp, Socket, Bin} ->
|
||||
case binary_to_term(Bin) of
|
||||
ok ->
|
||||
ok;
|
||||
{ok, Response} ->
|
||||
{ok, Response};
|
||||
{error, Reason} ->
|
||||
error_exit("Action failed with: ~tp", [Reason], ?LINE);
|
||||
Unexpected ->
|
||||
error_exit("Unexpected message: ~tp", [Unexpected], ?LINE)
|
||||
end;
|
||||
{tcp_closed, Socket} ->
|
||||
error_exit("Lost connection to node unexpectedly.", ?LINE)
|
||||
after 5000 ->
|
||||
error_exit("Connection timed out.", ?LINE)
|
||||
end.
|
||||
|
||||
|
||||
-spec connect_auth_or_die(zx:realm()) -> gen_tcp:socket() | no_return().
|
||||
|
||||
connect_auth_or_die(Realm) ->
|
||||
add_package2(Realm, Name) ->
|
||||
case connect_auth(Realm) of
|
||||
{ok, Socket} ->
|
||||
Socket;
|
||||
Error ->
|
||||
M1 = "Connection failed to realm prime with ~160tp.",
|
||||
ok = log(warning, M1, [Error]),
|
||||
halt(1)
|
||||
{ok, AuthConn} -> add_package3(Realm, Name, AuthConn);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
add_package3(Realm, Name, {UserName, KeyName, Key, Tag, Socket}) ->
|
||||
Command = 12,
|
||||
Package = {Realm, Name},
|
||||
Payload = {Tag, UserName, KeyName, Package},
|
||||
Request = pack_and_sign(Command, Payload, Key),
|
||||
ok = gen_tcp:send(Socket, <<0:8, Request/binary>>),
|
||||
done(Socket).
|
||||
|
||||
|
||||
|
||||
%%% Generic Request Forms
|
||||
|
||||
-spec make_uu_request(Command, Target) -> zx:outcome()
|
||||
when Command :: pos_integer(),
|
||||
Target :: zx:realm() | zx:package() | zx:package_id().
|
||||
|
||||
make_uu_request(Command, Target) when is_tuple(Target) ->
|
||||
make_uu_request2(Command, element(1, Target), Target);
|
||||
make_uu_request(Command, Target) when is_list(Target) ->
|
||||
make_uu_request2(Command, Target, Target).
|
||||
|
||||
make_uu_request2(Command, Realm, Target) ->
|
||||
case connect(Realm) of
|
||||
{ok, Socket} -> make_uu_request3(Command, Target, Socket);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
make_uu_request3(Command, Target, Socket) ->
|
||||
TermBin = term_to_binary(Target),
|
||||
Request = <<"ZOMP AUTH 1:", 0:24, Command:8, TermBin/binary>>,
|
||||
ok = gen_tcp:send(Socket, Request),
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<0:1, 0:7, Bin/binary>>} -> make_uu_request4(Bin);
|
||||
{tcp, Socket, Bin} -> done(Socket, Bin);
|
||||
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||
after 5000 -> done(Socket, timeout)
|
||||
end.
|
||||
|
||||
make_uu_request4(Bin) ->
|
||||
case zx_lib:b_to_ts(Bin) of
|
||||
error -> {error, bad_response};
|
||||
Term -> Term
|
||||
end.
|
||||
|
||||
|
||||
-spec make_su_request(Command, Realm, Data) -> zx:outcome()
|
||||
when Command :: 1 | 9 | 10 | 13..20,
|
||||
Realm :: zx:realm(),
|
||||
Data :: term().
|
||||
|
||||
make_su_request(Command, Realm, Data) ->
|
||||
AuthData = prep_auth(Realm),
|
||||
case connect(Realm) of
|
||||
{ok, Socket} -> make_su_request2(Command, Realm, Data, AuthData, Socket);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
make_su_request2(Command, Realm, Data, {Signatory, KeyName, Key}, Socket) ->
|
||||
Payload = {Realm, Signatory, KeyName, Data},
|
||||
Request = pack_and_sign(Command, Payload, Key),
|
||||
ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>),
|
||||
done(Socket).
|
||||
|
||||
|
||||
|
||||
%%% Connectiness with prime
|
||||
|
||||
-spec connect_auth(Realm) -> Result
|
||||
when Realm :: zx:realm(),
|
||||
Result :: {ok, gen_tcp:socket()}
|
||||
| {error, Reason :: term()}.
|
||||
when Realm :: zx:realm(),
|
||||
Result :: {ok, AuthConn}
|
||||
| {error, Reason},
|
||||
AuthConn :: {UserName :: zx:user_name(),
|
||||
KeyName :: zx:key_name(),
|
||||
Key :: term(),
|
||||
SSTag :: zx:ss_tag(),
|
||||
Socket :: gen_tcp:socket()},
|
||||
Reason :: term().
|
||||
%% @private
|
||||
%% Connect to one of the servers in the realm constellation.
|
||||
|
||||
connect_auth(Realm) ->
|
||||
RealmConf = zx_lib:load_realm_conf(Realm),
|
||||
{User, KeyID, Key} = prep_auth(Realm, RealmConf),
|
||||
{prime, {Host, Port}} = lists:keyfind(prime, 1, RealmConf),
|
||||
Options = [{packet, 4}, {mode, binary}, {active, true}],
|
||||
UserData = prep_auth(Realm),
|
||||
case zx_lib:load_realm_conf(Realm) of
|
||||
{ok, RealmConf} ->
|
||||
connect_auth2(Realm, RealmConf, UserData);
|
||||
Error ->
|
||||
ok = log(error, "Realm ~160tp is not configured.", [Realm]),
|
||||
Error
|
||||
end.
|
||||
|
||||
connect_auth2(Realm, RealmConf, UserData) ->
|
||||
{Host, Port} = maps:get(prime, RealmConf),
|
||||
Options = [{packet, 4}, {mode, binary}, {active, once}],
|
||||
case gen_tcp:connect(Host, Port, Options, 5000) of
|
||||
{ok, Socket} ->
|
||||
ok = log(info, "Connected to ~tp prime.", [Realm]),
|
||||
connect_auth(Socket, Realm, User, KeyID, Key);
|
||||
connect_auth3(Socket, Realm, UserData);
|
||||
Error = {error, E} ->
|
||||
ok = log(warning, "Connection problem: ~tp", [E]),
|
||||
ok = log(warning, "Connection problem: ~160tp", [E]),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
|
||||
-spec connect_auth(Socket, Realm, User, KeyID, Key) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Realm :: zx:realm(),
|
||||
User :: zx:user_id(),
|
||||
KeyID :: zx:key_id(),
|
||||
Key :: term(),
|
||||
Result :: {ok, gen_tcp:socket()}
|
||||
| {error, timeout}.
|
||||
%% @private
|
||||
%% Send a protocol ID string to notify the server what we're up to, disconnect
|
||||
%% if it does not return an "OK" response within 5 seconds.
|
||||
|
||||
connect_auth(Socket, Realm, User, KeyID, Key) ->
|
||||
ok = gen_tcp:send(Socket, <<"OTPR AUTH 1">>),
|
||||
connect_auth3(Socket, Realm, UD = {UserName, KeyName, Key}) ->
|
||||
Null = 0,
|
||||
Timestamp = calendar:universal_time(),
|
||||
Payload = {Realm, Timestamp, UserName, KeyName},
|
||||
NullRequest = pack_and_sign(Null, Payload, Key),
|
||||
ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", NullRequest/binary>>),
|
||||
receive
|
||||
{tcp, Socket, Bin} ->
|
||||
ok = binary_to_term(Bin, [safe]),
|
||||
confirm_auth(Socket, Realm, User, KeyID, Key);
|
||||
{tcp_closed, Socket} ->
|
||||
ok = log(warning, "Socket closed unexpectedly."),
|
||||
halt(1)
|
||||
after 5000 ->
|
||||
ok = log(warning, "Host realm ~160tp prime timed out.", [Realm]),
|
||||
{error, auth_timeout}
|
||||
{tcp, Socket, <<0:8, Bin/binary>>} -> connect_auth4(Socket, UD, Bin);
|
||||
{tcp, Socket, Bin} -> done(Socket, Bin);
|
||||
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||
after 5000 -> done(Socket, timeout)
|
||||
end.
|
||||
|
||||
connect_auth4(Socket, {UserName, KeyName, Key}, Bin) ->
|
||||
case zx_lib:b_to_ts(Bin) of
|
||||
{ok, Tag} -> {ok, {UserName, KeyName, Key, Tag, Socket}};
|
||||
error -> done(Socket, bad_response)
|
||||
end.
|
||||
|
||||
|
||||
confirm_auth(Socket, Realm, User, KeyID, Key) ->
|
||||
ok = send(Socket, {User, KeyID}),
|
||||
receive
|
||||
{tcp, Socket, Bin} ->
|
||||
case binary_to_term(Bin, [safe]) of
|
||||
{sign, Blob} ->
|
||||
Sig = public_key:sign(Blob, sha512, Key),
|
||||
ok = send(Socket, {signed, Sig}),
|
||||
confirm_auth(Socket);
|
||||
{error, not_prime} ->
|
||||
M1 = "Connected node is not prime for realm ~160tp",
|
||||
ok = log(warning, M1, [Realm]),
|
||||
ok = disconnect(Socket),
|
||||
{error, not_prime};
|
||||
{error, bad_user} ->
|
||||
M2 = "Bad user record ~160tp",
|
||||
ok = log(warning, M2, [User]),
|
||||
ok = disconnect(Socket),
|
||||
{error, bad_user};
|
||||
{error, unauthorized_key} ->
|
||||
M3 = "Unauthorized user key ~160tp",
|
||||
ok = log(warning, M3, [KeyID]),
|
||||
ok = disconnect(Socket),
|
||||
{error, unauthorized_key};
|
||||
{error, Reason} ->
|
||||
Message = "Could not begin auth exchange. Failed with ~160tp",
|
||||
ok = log(warning, Message, [Reason]),
|
||||
ok = disconnect(Socket),
|
||||
{error, Reason}
|
||||
end;
|
||||
{tcp_closed, Socket} ->
|
||||
ok = log(warning, "Socket closed unexpectedly."),
|
||||
halt(1)
|
||||
after 5000 ->
|
||||
ok = log(warning, "Host realm ~tp prime timed out.", [Realm]),
|
||||
{error, auth_timeout}
|
||||
connect(Realm) ->
|
||||
case zx_lib:load_realm_conf(Realm) of
|
||||
{ok, RealmConf} ->
|
||||
{Host, Port} = maps:get(prime, RealmConf),
|
||||
Options = [{packet, 4}, {mode, binary}, {nodelay, true}, {active, once}],
|
||||
gen_tcp:connect(Host, Port, Options, 5000);
|
||||
Error ->
|
||||
ok = log(error, "Realm ~160tp is not configured.", [Realm]),
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
confirm_auth(Socket) ->
|
||||
receive
|
||||
{tcp, Socket, Bin} ->
|
||||
case binary_to_term(Bin, [safe]) of
|
||||
ok -> {ok, Socket};
|
||||
Other -> {error, Other}
|
||||
end;
|
||||
{tcp_closed, Socket} ->
|
||||
ok = log(warning, "Socket closed unexpectedly."),
|
||||
halt(1)
|
||||
after 5000 ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
-spec prep_auth(Realm, RealmConf) -> {User, KeyID, Key} | no_return()
|
||||
when Realm :: zx:realm(),
|
||||
RealmConf :: [term()],
|
||||
User :: zx:user_id(),
|
||||
KeyID :: zx:key_id(),
|
||||
Key :: term().
|
||||
-spec prep_auth(Realm) -> {User, KeyName, Key}
|
||||
when Realm :: zx:realm(),
|
||||
User :: zx:user_id(),
|
||||
KeyName :: zx:key_id(),
|
||||
Key :: term().
|
||||
%% @private
|
||||
%% Loads the appropriate User, KeyID and reads in a registered key for use in
|
||||
%% connect_auth/4.
|
||||
|
||||
prep_auth(Realm, RealmConf) ->
|
||||
UsersFile = filename:join(zx_lib:zomp_dir(), "zomp.users"),
|
||||
Users =
|
||||
case file:consult(UsersFile) of
|
||||
{ok, U} ->
|
||||
U;
|
||||
{error, enoent} ->
|
||||
ok = log(warning, "You do not have any users configured."),
|
||||
halt(1)
|
||||
end,
|
||||
{User, KeyIDs} =
|
||||
case lists:keyfind(Realm, 1, Users) of
|
||||
{Realm, UserName, []} ->
|
||||
W = "User ~tp does not have any keys registered for realm ~tp.",
|
||||
ok = log(warning, W, [UserName, Realm]),
|
||||
ok = log(info, "Contact the following sysop(s) to register a key:"),
|
||||
{sysops, Sysops} = lists:keyfind(sysops, 1, RealmConf),
|
||||
PrintContact =
|
||||
fun({_, _, Email, Name, _, _}) ->
|
||||
log(info, "Sysop: ~ts Email: ~ts", [Name, Email])
|
||||
end,
|
||||
ok = lists:foreach(PrintContact, Sysops),
|
||||
halt(1);
|
||||
{Realm, UserName, KeyNames} ->
|
||||
KIDs = [{Realm, KeyName} || KeyName <- KeyNames],
|
||||
{{Realm, UserName}, KIDs};
|
||||
false ->
|
||||
Message = "You are not a user of the given realm: ~160tp.",
|
||||
ok = log(warning, Message, [Realm]),
|
||||
halt(1)
|
||||
end,
|
||||
KeyID = hd(KeyIDs),
|
||||
true = zx_key:ensure_keypair(KeyID),
|
||||
{ok, Key} = zx_key:load(private, KeyID),
|
||||
{User, KeyID, Key}.
|
||||
prep_auth(Realm) ->
|
||||
UserName = zx_local:select_user(Realm),
|
||||
KeyName = zx_local:select_private_key({Realm, UserName}),
|
||||
{ok, Key} = zx_key:load(private, {Realm, KeyName}),
|
||||
{UserName, KeyName, Key}.
|
||||
|
||||
|
||||
-spec disconnect(gen_tcp:socket()) -> ok.
|
||||
%% @private
|
||||
%% Gracefully shut down a socket, logging (but sidestepping) the case when the socket
|
||||
%% has already been closed by the other side.
|
||||
pack_and_sign(Command, Payload, Key) ->
|
||||
Bin = term_to_binary(Payload),
|
||||
Signed = <<Command:8, Bin/binary>>,
|
||||
Sig = public_key:sign(Signed, sha512, Key),
|
||||
SSize = byte_size(Sig),
|
||||
<<SSize:24, Sig/binary, Signed/binary>>.
|
||||
|
||||
disconnect(Socket) ->
|
||||
case gen_tcp:shutdown(Socket, read_write) of
|
||||
ok ->
|
||||
log(info, "Disconnected from ~tp", [Socket]);
|
||||
{error, Error} ->
|
||||
Message = "Shutdown connection ~p failed with: ~p",
|
||||
log(warning, Message, [Socket, Error])
|
||||
|
||||
done(Socket) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<0:1, 0:7>>} ->
|
||||
zx_net:disconnect(Socket);
|
||||
{tcp, Socket, Bin} ->
|
||||
ok = zx_net:disconnect(Socket),
|
||||
{error, zx_net:err_in(Bin)};
|
||||
{tcp_closed, Socket} ->
|
||||
{error, tcp_closed}
|
||||
after 5000 ->
|
||||
done(Socket, timeout)
|
||||
end.
|
||||
|
||||
|
||||
|
||||
%%% Error exits
|
||||
|
||||
-spec error_exit(Error, Line) -> no_return()
|
||||
when Error :: term(),
|
||||
Line :: non_neg_integer().
|
||||
%% @private
|
||||
%% Format an error message in a way that makes it easy to locate.
|
||||
|
||||
error_exit(Error, Line) ->
|
||||
error_exit(Error, [], Line).
|
||||
|
||||
|
||||
-spec error_exit(Format, Args, Line) -> no_return()
|
||||
when Format :: string(),
|
||||
Args :: [term()],
|
||||
Line :: non_neg_integer().
|
||||
%% @private
|
||||
%% Format an error message in a way that makes it easy to locate.
|
||||
|
||||
error_exit(Format, Args, Line) ->
|
||||
File = filename:basename(?FILE),
|
||||
ok = log(error, "~ts:~tp: " ++ Format, [File, Line | Args]),
|
||||
halt(1).
|
||||
done(Socket, Reason) ->
|
||||
ok = zx_net:disconnect(Socket),
|
||||
case is_binary(Reason) of
|
||||
true -> {error, zx_net:err_in(Reason)};
|
||||
false -> {error, Reason}
|
||||
end.
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([subscribe/2, unsubscribe/2, fetch/3, query/3]).
|
||||
-export([subscribe/2, unsubscribe/2, request/3]).
|
||||
-export([start/1, stop/1]).
|
||||
-export([start_link/1, init/2]).
|
||||
|
||||
@ -38,31 +38,17 @@ unsubscribe(Conn, Package) ->
|
||||
ok.
|
||||
|
||||
|
||||
-spec fetch(Conn, ID, Object) -> ok
|
||||
when Conn :: pid(),
|
||||
ID :: zx_daemon:id(),
|
||||
Object :: zx_daemon:object().
|
||||
%% @doc
|
||||
%% This requests a package be fetched from upstream.
|
||||
%% Only to be called by zx_daemon.
|
||||
%% Results must be returned with the ID via zx_daemon:result/2.
|
||||
|
||||
fetch(Conn, ID, Object) ->
|
||||
Conn ! {fetch, ID, Object},
|
||||
ok.
|
||||
|
||||
|
||||
-spec query(Conn, ID, Action) -> ok
|
||||
-spec request(Conn, ID, Action) -> ok
|
||||
when Conn :: pid(),
|
||||
ID :: zx_daemon:id(),
|
||||
Action :: zx_daemon:action().
|
||||
%% @doc
|
||||
%% Wraps any legal query.
|
||||
%% Wraps any legal request.
|
||||
%% Only to be called by zx_daemon.
|
||||
%% Results must be returned with the ID via zx_daemon:result/2.
|
||||
|
||||
query(Conn, ID, Action) ->
|
||||
Conn ! {query, ID, Action},
|
||||
request(Conn, ID, Action) ->
|
||||
Conn ! {request, ID, Action},
|
||||
ok.
|
||||
|
||||
|
||||
@ -109,7 +95,6 @@ start_link(Target) ->
|
||||
%% gen_server callback. For more information refer to the OTP documentation.
|
||||
|
||||
init(Parent, Target) ->
|
||||
ok = log(info, "Connecting to ~tp", [Target]),
|
||||
Debug = sys:debug_options([]),
|
||||
ok = proc_lib:init_ack(Parent, {ok, self()}),
|
||||
connect(Parent, Debug, Target).
|
||||
@ -124,12 +109,12 @@ init(Parent, Target) ->
|
||||
Target :: zx:host().
|
||||
|
||||
connect(Parent, Debug, {Host, Port}) ->
|
||||
Options = [{packet, 4}, {mode, binary}, {nodelay, true}, {active, true}],
|
||||
Options = [{packet, 4}, {mode, binary}, {nodelay, true}, {active, once}],
|
||||
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", [Host, Error]),
|
||||
ok = log(warning, "Connection problem with ~160tp: ~160tp", [Host, Error]),
|
||||
ok = zx_daemon:report(failed),
|
||||
terminate()
|
||||
end.
|
||||
@ -144,11 +129,13 @@ connect(Parent, Debug, {Host, Port}) ->
|
||||
%% another node.
|
||||
|
||||
confirm_service(Parent, Debug, Socket) ->
|
||||
ok = gen_tcp:send(Socket, <<"LEAF 1:">>),
|
||||
Realms = zx_lib:list_realms(),
|
||||
Message = <<"ZOMP LEAF 1:", (term_to_binary(Realms))/binary>>,
|
||||
ok = gen_tcp:send(Socket, Message),
|
||||
receive
|
||||
{tcp, Socket, <<0:8, RealmsBin/binary>>} ->
|
||||
{ok, Realms} = zx_lib:b_to_ts(RealmsBin),
|
||||
ok = zx_daemon:report({connected, Realms}),
|
||||
{ok, Available} = zx_lib:b_to_ts(RealmsBin),
|
||||
ok = zx_daemon:report({connected, Available}),
|
||||
loop(Parent, Debug, Socket);
|
||||
{tcp, Socket, <<1:8, HostsBin/binary>>} ->
|
||||
{ok, Hosts} = zx_lib:b_to_ts(HostsBin),
|
||||
@ -198,29 +185,26 @@ loop(Parent, Debug, Socket) ->
|
||||
ok = zx_daemon:report({serial_update, Realm, Serial}),
|
||||
loop(Parent, Debug, Socket);
|
||||
{tcp, Socket, Unexpected} ->
|
||||
ok = log(warning, "Funky data from node: ~tp", [Unexpected]),
|
||||
ok = log(warning, "Funky data from node: ~160tp", [Unexpected]),
|
||||
ok = zx_net:disconnect(Socket),
|
||||
terminate();
|
||||
{request, ID, Action} ->
|
||||
Result = dispatch(Socket, ID, Action),
|
||||
ok = zx_daemon:result(ID, Result),
|
||||
loop(Parent, Debug, Socket);
|
||||
{subscribe, Package} ->
|
||||
ok = do_subscribe(Socket, Package),
|
||||
loop(Parent, Debug, Socket);
|
||||
{unsubscribe, Package} ->
|
||||
ok = do_unsubscribe(Socket, Package),
|
||||
loop(Parent, Debug, Socket);
|
||||
{query, ID, Action} ->
|
||||
Result = do_query(Socket, Action),
|
||||
ok = zx_daemon:result(ID, Result),
|
||||
loop(Parent, Debug, Socket);
|
||||
{fetch, ID, PackageID} ->
|
||||
ok = do_fetch(Socket, ID, PackageID),
|
||||
loop(Parent, Debug, Socket);
|
||||
stop ->
|
||||
ok = zx_net:disconnect(Socket),
|
||||
terminate();
|
||||
{tcp_closed, Socket} ->
|
||||
handle_unexpected_close();
|
||||
Unexpected ->
|
||||
ok = log(warning, "Unexpected message: ~tp", [Unexpected]),
|
||||
ok = log(warning, "Unexpected message: ~160tp", [Unexpected]),
|
||||
loop(Parent, Debug, Socket)
|
||||
end.
|
||||
|
||||
@ -235,7 +219,7 @@ handle_package_update(Sig, Bin) ->
|
||||
true ->
|
||||
zx_daemon:notify(Package, {update, PackageID});
|
||||
false ->
|
||||
ok = log(error, "Received an unverified update message: ~tp", [Message]),
|
||||
ok = log(error, "Received an unverified update message: ~160tp", [Message]),
|
||||
terminate()
|
||||
end.
|
||||
|
||||
@ -260,26 +244,33 @@ do_unsubscribe(Socket, Package) ->
|
||||
wait_ok(Socket).
|
||||
|
||||
|
||||
do_query(Socket, {list, Realm}) ->
|
||||
Reference = term_to_binary(Realm),
|
||||
Message = <<0:1, 3:7, Reference/binary>>,
|
||||
send_query(Socket, Message);
|
||||
do_query(Socket, {list, Realm, Name}) ->
|
||||
Reference = term_to_binary({Realm, Name}),
|
||||
Message = <<0:1, 4:7, Reference/binary>>,
|
||||
send_query(Socket, Message);
|
||||
do_query(Socket, {list, Realm, Name, Version}) ->
|
||||
Reference = term_to_binary({Realm, Name, Version}),
|
||||
Message = <<0:1, 4:7, Reference/binary>>,
|
||||
send_query(Socket, Message);
|
||||
do_query(Socket, {latest, Realm, Name}) ->
|
||||
Reference = term_to_binary({Realm, Name}),
|
||||
Message = <<0:1, 6:7, Reference/binary>>,
|
||||
send_query(Socket, Message);
|
||||
do_query(Socket, {latest, Realm, Name, Version}) ->
|
||||
Reference = term_to_binary({Realm, Name, Version}),
|
||||
Message = <<0:1, 6:7, Reference/binary>>,
|
||||
send_query(Socket, Message).
|
||||
-spec wait_ok(gen_tcp:socket()) -> ok | no_return().
|
||||
|
||||
wait_ok(Socket) ->
|
||||
receive
|
||||
{tcp, Socket, <<0:1, 0:7>>} -> ok
|
||||
after 5000 -> handle_timeout(Socket)
|
||||
end.
|
||||
|
||||
|
||||
dispatch(Socket, ID, Action) ->
|
||||
case Action of
|
||||
{list, R} ->
|
||||
send_query(Socket, <<0:1, 3:7, (term_to_binary(R))/binary>>);
|
||||
{list, R, N} ->
|
||||
send_query(Socket, <<0:1, 4:7, (term_to_binary({R, N}))/binary>>);
|
||||
{list, R, N, V} ->
|
||||
send_query(Socket, <<0:1, 4:7, (term_to_binary({R, N, V}))/binary>>);
|
||||
{latest, R, N} ->
|
||||
send_query(Socket, <<0:1, 5:7, (term_to_binary({R, N}))/binary>>);
|
||||
{latest, R, N, V} ->
|
||||
send_query(Socket, <<0:1, 5:7, (term_to_binary({R, N, V}))/binary>>);
|
||||
{fetch, R, N, V} ->
|
||||
make_fetch(Socket, ID, {R, N, V});
|
||||
_ ->
|
||||
Message = "Received unexpected request action. ID: ~tp, Action: ~200tp",
|
||||
log(warning, Message, [ID, Action])
|
||||
end.
|
||||
|
||||
|
||||
send_query(Socket, Message) ->
|
||||
@ -290,105 +281,67 @@ send_query(Socket, Message) ->
|
||||
wait_query(Socket) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<1:1, 0:7>>} ->
|
||||
ok = pong(Socket),
|
||||
wait_query(Socket);
|
||||
{tcp, Socket, <<0:1, 0:7, Bin/binary>>} ->
|
||||
{ok, Result} = zx_lib:b_to_ts(Bin),
|
||||
Result;
|
||||
{tcp, Socket, <<0:1, 1:7>>} ->
|
||||
ok = zx_daemon:report(failed),
|
||||
terminate();
|
||||
{tcp, Socket, <<0:1, 2:7>>} ->
|
||||
{error, bad_realm};
|
||||
{tcp, Socket, <<0:1, 3:7>>} ->
|
||||
{error, bad_package};
|
||||
{tcp, Socket, <<0:1, 4:7>>} ->
|
||||
{error, bad_version}
|
||||
after 5000 ->
|
||||
handle_timeout(Socket)
|
||||
{tcp, Socket, <<1:1, 0:7>>} -> wait_pong(Socket);
|
||||
{tcp, Socket, <<0:1, 0:7>>} -> ok;
|
||||
{tcp, Socket, <<0:1, 0:7, Bin/binary>>} -> zx_lib:b_to_ts(Bin);
|
||||
{tcp, Socket, Bin} -> {error, zx_net:err_in(Bin)}
|
||||
after 5000 -> handle_timeout(Socket)
|
||||
end.
|
||||
|
||||
|
||||
wait_pong(Socket) ->
|
||||
ok = pong(Socket),
|
||||
wait_query(Socket).
|
||||
|
||||
|
||||
pong(Socket) ->
|
||||
gen_tcp:send(Socket, <<1:1, 0:7>>).
|
||||
|
||||
|
||||
-spec do_fetch(Socket, ID, PackageID) -> Result
|
||||
-spec make_fetch(Socket, ID, PackageID) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
ID :: zx_daemon:id(),
|
||||
PackageID :: zx:package_id(),
|
||||
Result :: ok.
|
||||
Result :: {done, binary()}.
|
||||
%% @private
|
||||
%% Download a package to the local cache.
|
||||
|
||||
do_fetch(Socket, ID, PackageID) ->
|
||||
Reference = binary_to_term(PackageID),
|
||||
Message = <<0:1, 7:7, Reference/binary>>,
|
||||
make_fetch(Socket, ID, PackageID) ->
|
||||
TermBin = term_to_binary(PackageID),
|
||||
Message = <<0:1, 6:7, TermBin/binary>>,
|
||||
ok = gen_tcp:send(Socket, Message),
|
||||
ok = wait_hops(Socket, ID),
|
||||
{ok, Bin} = receive_zsp(Socket),
|
||||
zx_daemon:result(ID, {done, Bin}).
|
||||
{ok, Bin} = zx_net:rx(Socket),
|
||||
{done, Bin}.
|
||||
|
||||
|
||||
wait_hops(Socket, ID) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<0:1, 0:7, 0:8>>} ->
|
||||
ok = inet:setopts(Socket, [{packet, 0}]),
|
||||
gen_tcp:send(Socket, <<0:1, 0:7>>);
|
||||
ok;
|
||||
{tcp, Socket, <<0:1, 0:7, Distance:8>>} ->
|
||||
ok = zx_daemon:result(ID, {hops, Distance}),
|
||||
wait_hops(Socket, ID);
|
||||
{tcp, Socket, <<0:1, 1:7>>} ->
|
||||
{error, bad_message};
|
||||
{tcp, Socket, <<0:1, 2:7>>} ->
|
||||
{error, bad_realm};
|
||||
{tcp, Socket, <<0:1, 3:7>>} ->
|
||||
{error, bad_package};
|
||||
{tcp, Socket, <<0:1, 4:7>>} ->
|
||||
{error, bad_version};
|
||||
{tcp, Socket, <<0:1, 5:7>>} ->
|
||||
handle_timeout(Socket)
|
||||
handle_timeout(Socket);
|
||||
{tcp, Socket, Bin} ->
|
||||
Reason = zx_net:err_in(Bin),
|
||||
ok = log(error, "Failed in wait_hops/2 with reason: ~tp", [Reason]),
|
||||
ok = zx_net:disconnect(Socket),
|
||||
terminate()
|
||||
after 60000 ->
|
||||
handle_timeout(Socket)
|
||||
end.
|
||||
|
||||
|
||||
receive_zsp(Socket) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<Size:32, Bin:Size/binary>>} ->
|
||||
{ok, Bin};
|
||||
{tcp, Socket, <<Total:32, Bin/binary>>} ->
|
||||
Size = byte_size(Bin),
|
||||
receive_zsp(Socket, Total, Size, Bin)
|
||||
after 5000 ->
|
||||
handle_timeout(Socket)
|
||||
end.
|
||||
|
||||
|
||||
receive_zsp(Socket, Total, SoFar, Bin) when Total > SoFar ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, New} ->
|
||||
Size = byte_size(New),
|
||||
NewBin = <<Bin/binary, New/binary>>,
|
||||
receive_zsp(Socket, Total, Size + SoFar, NewBin)
|
||||
after 5000 ->
|
||||
handle_timeout(Socket)
|
||||
end;
|
||||
receive_zsp(Socket, Total, Total, Bin) ->
|
||||
ok = inet:setopts(Socket, [{packet, 4}]),
|
||||
ok = gen_tcp:send(Socket, <<0:1, 0:7>>),
|
||||
{ok, Bin}.
|
||||
|
||||
|
||||
%%% Terminal handlers
|
||||
|
||||
-spec handle_unexpected_close() -> no_return().
|
||||
|
||||
handle_unexpected_close() ->
|
||||
ok = log(info, "Connection closed unexpectedly."),
|
||||
ok = zx_daemon:report(disconnected),
|
||||
terminate().
|
||||
|
||||
@ -401,15 +354,6 @@ handle_timeout(Socket) ->
|
||||
terminate().
|
||||
|
||||
|
||||
-spec wait_ok(gen_tcp:socket()) -> ok | no_return().
|
||||
|
||||
wait_ok(Socket) ->
|
||||
receive
|
||||
{tcp, Socket, <<0:1, 0:7>>} -> ok
|
||||
after 5000 -> handle_timeout(Socket)
|
||||
end.
|
||||
|
||||
|
||||
-spec terminate() -> no_return().
|
||||
%% @private
|
||||
%% Convenience wrapper around the suicide call.
|
||||
|
||||
@ -144,11 +144,11 @@
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([declare_proxy/0]).
|
||||
-export([pass_meta/3,
|
||||
subscribe/1, unsubscribe/1,
|
||||
list/0, list/1, list/2, list/3, latest/1,
|
||||
verify_key/1, fetch/1, install/1,
|
||||
pending/1, packagers/1, maintainers/1, sysops/1]).
|
||||
verify_key/1, fetch/1, install/1, build/1]).
|
||||
-export([report/1, result/2, notify/2]).
|
||||
-export([start_link/0, init_connections/0, stop/0]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
@ -158,7 +158,6 @@
|
||||
-export_type([id/0, result/0,
|
||||
realm_list/0, package_list/0, version_list/0, latest_result/0,
|
||||
fetch_result/0, key_result/0,
|
||||
pending_result/0, pack_result/0, maint_result/0, sysop_result/0,
|
||||
sub_message/0]).
|
||||
|
||||
|
||||
@ -199,11 +198,11 @@
|
||||
|
||||
|
||||
-record(conn,
|
||||
{pid :: pid(),
|
||||
host :: zx:host(),
|
||||
realms :: [zx:realm()],
|
||||
requests :: [id()],
|
||||
subs :: [{pid(), zx:package()}]}).
|
||||
{pid = none :: none | pid(),
|
||||
host = none :: none | zx:host(),
|
||||
realms = [] :: [zx:realm()],
|
||||
requests = [] :: [id()],
|
||||
subs = [] :: [{pid(), zx:package()}]}).
|
||||
|
||||
|
||||
%% State Types
|
||||
@ -237,6 +236,8 @@
|
||||
| {list, zx:realm(), zx:name(), zx:version()}
|
||||
| {latest, zx:realm(), zx:name()}
|
||||
| {latest, zx:realm(), zx:name(), zx:version()}
|
||||
| {pending, zx:realm(), zx:name()}
|
||||
| {approved, zx:realm()}
|
||||
| {fetch, zx:realm(), zx:name(), zx:version()}
|
||||
| {fetchkey, zx:realm(), zx:key_name()}.
|
||||
|
||||
@ -254,11 +255,7 @@
|
||||
| version_list()
|
||||
| latest_result()
|
||||
| fetch_result()
|
||||
| key_result()
|
||||
| pending_result()
|
||||
| pack_result()
|
||||
| maint_result()
|
||||
| sysop_result()}.
|
||||
| key_result()}.
|
||||
|
||||
-type realm_list() :: [zx:realm()].
|
||||
-type package_list() :: {ok, [zx:name()]}
|
||||
@ -274,7 +271,7 @@
|
||||
| bad_version
|
||||
| timeout}.
|
||||
-type fetch_result() :: {hops, non_neg_integer()}
|
||||
| done
|
||||
| {done, zx:package_id()}
|
||||
| {error, bad_realm
|
||||
| bad_package
|
||||
| bad_version
|
||||
@ -283,21 +280,6 @@
|
||||
| {error, bad_realm
|
||||
| bad_key
|
||||
| timeout}.
|
||||
-type pending_result() :: {ok, [zx:version()]}
|
||||
| {error, bad_realm
|
||||
| bad_package
|
||||
| timeout}.
|
||||
-type pack_result() :: {ok, [zx:user()]}
|
||||
| {error, bad_realm
|
||||
| bad_package
|
||||
| timeout}.
|
||||
-type maint_result() :: {ok, [zx:user()]}
|
||||
| {error, bad_realm
|
||||
| bad_package
|
||||
| timeout}.
|
||||
-type sysop_result() :: {ok, [zx:user()]}
|
||||
| {error, bad_host
|
||||
| timeout}.
|
||||
|
||||
|
||||
% Subscription Results
|
||||
@ -306,6 +288,13 @@
|
||||
Message :: {update, zx:package_id()}
|
||||
| {error, bad_realm | bad_package}}.
|
||||
|
||||
%%% Zomp Interface
|
||||
|
||||
-spec declare_proxy() -> ok.
|
||||
|
||||
declare_proxy() ->
|
||||
log(info, "Would be claiming this node for myself right now...").
|
||||
|
||||
|
||||
%%% Requestor Interface
|
||||
|
||||
@ -320,9 +309,6 @@
|
||||
%% references.
|
||||
|
||||
pass_meta(Meta, Dir, ArgV) ->
|
||||
ok = log(info, "Meta: ~tp", [Meta]),
|
||||
ok = log(info, "Dir : ~tp", [Dir]),
|
||||
ok = log(info, "ArgV: ~tp", [ArgV]),
|
||||
gen_server:cast(?MODULE, {pass_meta, Meta, Dir, ArgV}).
|
||||
|
||||
|
||||
@ -460,6 +446,15 @@ verify_key({Realm, KeyName}) ->
|
||||
request({verify_key, Realm, KeyName}).
|
||||
|
||||
|
||||
-spec fetch(zx:package_id()) -> {ok, id()}.
|
||||
%% @doc
|
||||
%% Install the specified package. This returns an id() that will be referenced
|
||||
%% in a later response message.
|
||||
|
||||
fetch(PackageID) ->
|
||||
gen_server:call(?MODULE, {fetch, PackageID}).
|
||||
|
||||
|
||||
-spec install(Path :: file:filename()) -> zx:outcome().
|
||||
%% @doc
|
||||
%% Install a package from a local file.
|
||||
@ -468,83 +463,10 @@ install(Path) ->
|
||||
gen_server:call(?MODULE, {install, Path}).
|
||||
|
||||
|
||||
-spec fetch(PackageString :: string()) -> {ok, id()}.
|
||||
%% @doc
|
||||
%% Install the specified package. This returns an id() that will be referenced
|
||||
%% in a later response message.
|
||||
-spec build(zx:package_id()) -> zx:outcome().
|
||||
|
||||
fetch(PackageString) ->
|
||||
case zx_lib:package_id(PackageString) of
|
||||
{ok, PackageID} ->
|
||||
gen_server:call(?MODULE, {fetch, PackageID});
|
||||
{error, invalid_package_string} ->
|
||||
{error, "Invalid package string.", 22}
|
||||
end.
|
||||
|
||||
|
||||
-spec pending(Package) -> {ok, RequestID}
|
||||
when Package :: zx:package(),
|
||||
RequestID :: id().
|
||||
%% @doc
|
||||
%% Request the list of versions of a given package that have been submitted but not
|
||||
%% signed and included in their relevant realm.
|
||||
%% Crashes the caller if either component of the Package is illegal.
|
||||
%%
|
||||
%% Response messages are of the type `result()' where the third element is of the
|
||||
%% type `pending_result()'.
|
||||
|
||||
pending({Realm, Name}) ->
|
||||
true = zx_lib:valid_lower0_9(Realm),
|
||||
true = zx_lib:valid_lower0_9(Name),
|
||||
request({pending, Realm, Name}).
|
||||
|
||||
|
||||
-spec packagers(Package) -> {ok, RequestID}
|
||||
when Package :: zx:package(),
|
||||
RequestID :: id().
|
||||
%% @doc
|
||||
%% Request a list of packagers assigned to work on a given package.
|
||||
%% Crashes the caller if either component of the Package is illegal.
|
||||
%%
|
||||
%% Response messages are of the type `result()' where the third element is of the
|
||||
%% type `pack_result()'.
|
||||
|
||||
packagers({Realm, Name}) ->
|
||||
true = zx_lib:valid_lower0_9(Realm),
|
||||
true = zx_lib:valid_lower0_9(Name),
|
||||
request({packagers, Realm, Name}).
|
||||
|
||||
|
||||
-spec maintainers(Package) -> {ok, RequestID}
|
||||
when Package :: zx:package(),
|
||||
RequestID :: id().
|
||||
%% @doc
|
||||
%% Request a list of maintainers assigned to work on a given package.
|
||||
%% Crashes the caller if either component of the Package is illegal.
|
||||
%%
|
||||
%% Response messages are of the type `result()' where the third element is of the
|
||||
%% type `maint_result()'.
|
||||
|
||||
maintainers({Realm, Name}) ->
|
||||
true = zx_lib:valid_lower0_9(Realm),
|
||||
true = zx_lib:valid_lower0_9(Name),
|
||||
request({maintainers, Realm, Name}).
|
||||
|
||||
|
||||
-spec sysops(Realm) -> {ok, RequestID}
|
||||
when Realm :: zx:realm(),
|
||||
RequestID :: id().
|
||||
%% @doc
|
||||
%% Request a list of sysops in charge of maintaining a given realm. What this
|
||||
%% effectively does is request the sysops of the prime host of the given realm.
|
||||
%% Crashes the caller if the Realm string is illegal.
|
||||
%%
|
||||
%% Response messages are of the type `result()' where the third element is of the
|
||||
%% type `sysops_result()'.
|
||||
|
||||
sysops(Realm) ->
|
||||
true = zx_lib:valid_lower0_9(Realm),
|
||||
request({sysops, Realm}).
|
||||
build(PackageID) ->
|
||||
gen_server:call(?MODULE, {build, PackageID}).
|
||||
|
||||
|
||||
%% Request Caster
|
||||
@ -655,8 +577,12 @@ handle_call({install, Path}, _, State) ->
|
||||
Result = do_import_zsp(Path),
|
||||
NewState = eval_queue(State),
|
||||
{reply, Result, NewState};
|
||||
handle_call({build, PackageID}, _, State) ->
|
||||
Result = do_build(PackageID),
|
||||
NewState = eval_queue(State),
|
||||
{reply, Result, NewState};
|
||||
handle_call(Unexpected, From, State) ->
|
||||
ok = log(warning, "Unexpected call ~tp: ~tp", [From, Unexpected]),
|
||||
ok = log(warning, "Unexpected call ~160tp: ~160tp", [From, Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
@ -692,7 +618,7 @@ handle_cast(init_connections, State) ->
|
||||
handle_cast(stop, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(Unexpected, State) ->
|
||||
ok = log(warning, "Unexpected cast: ~tp", [Unexpected]),
|
||||
ok = log(warning, "Unexpected cast: ~160tp", [Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
@ -703,7 +629,7 @@ handle_info({'DOWN', Ref, process, Pid, Reason}, State) ->
|
||||
NewState = clear_monitor(Pid, Ref, Reason, State),
|
||||
{noreply, NewState};
|
||||
handle_info(Unexpected, State) ->
|
||||
ok = log(warning, "Unexpected info: ~tp", [Unexpected]),
|
||||
ok = log(warning, "Unexpected info: ~160tp", [Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
@ -726,7 +652,7 @@ terminate(normal, #s{cx = CX}) ->
|
||||
ok ->
|
||||
log(info, "Cache written.");
|
||||
{error, Reason} ->
|
||||
Message = "Cache write failed with ~tp",
|
||||
Message = "Cache write failed with ~160tp",
|
||||
log(error, Message, [Reason])
|
||||
end.
|
||||
|
||||
@ -742,9 +668,6 @@ terminate(normal, #s{cx = CX}) ->
|
||||
NewState :: state().
|
||||
|
||||
do_pass_meta(Meta, Home, ArgV, State) ->
|
||||
PackageID = maps:get(package_id, Meta),
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
ok = log(info, "Received meta for ~tp.", [PackageString]),
|
||||
State#s{meta = Meta, home = Home, argv = ArgV}.
|
||||
|
||||
|
||||
@ -756,9 +679,10 @@ do_pass_meta(Meta, Home, ArgV, State) ->
|
||||
%% @private
|
||||
%% Enqueue a subscription request.
|
||||
|
||||
do_subscribe(Pid, Package, State = #s{actions = Actions}) ->
|
||||
do_subscribe(Pid, Package, State = #s{actions = Actions, mx = MX}) ->
|
||||
NewActions = [{subscribe, Pid, Package} | Actions],
|
||||
State#s{actions = NewActions}.
|
||||
NewMX = mx_add_monitor(Pid, {subscriber, Package}, MX),
|
||||
State#s{actions = NewActions, mx = NewMX}.
|
||||
|
||||
|
||||
-spec do_unsubscribe(Pid, Package, State) -> NextState
|
||||
@ -769,9 +693,10 @@ do_subscribe(Pid, Package, State = #s{actions = Actions}) ->
|
||||
%% @private
|
||||
%% Clear or dequeue a subscription request.
|
||||
|
||||
do_unsubscribe(Pid, Package, State = #s{actions = Actions}) ->
|
||||
do_unsubscribe(Pid, Package, State = #s{actions = Actions, mx = MX}) ->
|
||||
{ok, NewMX} = mx_del_monitor(Pid, {subscription, Package}, MX),
|
||||
NewActions = [{unsubscribe, Pid, Package} | Actions],
|
||||
State#s{actions = NewActions}.
|
||||
State#s{actions = NewActions, mx = NewMX}.
|
||||
|
||||
|
||||
-spec do_request(Requestor, Action, State) -> NextState
|
||||
@ -782,9 +707,10 @@ do_unsubscribe(Pid, Package, State = #s{actions = Actions}) ->
|
||||
%% @private
|
||||
%% Enqueue requests and update relevant index.
|
||||
|
||||
do_request(Requestor, Action, State = #s{id = ID, actions = Actions}) ->
|
||||
do_request(Requestor, Action, State = #s{id = ID, actions = Actions, mx = MX}) ->
|
||||
NewActions = [{request, Requestor, ID, Action} | Actions],
|
||||
State#s{actions = NewActions}.
|
||||
NewMX = mx_add_monitor(Requestor, {requestor, ID}, MX),
|
||||
State#s{actions = NewActions, mx = NewMX}.
|
||||
|
||||
|
||||
-spec do_report(Conn, Message, State) -> NewState
|
||||
@ -820,7 +746,7 @@ 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]),
|
||||
ok = log(warning, "Connection ~160tp timed out.", [Conn]),
|
||||
NewMX = mx_del_monitor(Conn, conn, MX),
|
||||
disconnected(Conn, State#s{mx = NewMX}).
|
||||
|
||||
@ -1024,11 +950,11 @@ handle_fetch_result(ID, Outcome, {Requestor, _}, Requests, MX) ->
|
||||
handle_orphan_result(ID, Result, Dropped) ->
|
||||
case maps:take(ID, Dropped) of
|
||||
{Request, NewDropped} ->
|
||||
Message = "Received orphan result for ~tp, ~tp: ~tp",
|
||||
Message = "Received orphan result for ~160tp, ~160tp: ~160tp",
|
||||
ok = log(info, Message, [ID, Request, Result]),
|
||||
NewDropped;
|
||||
error ->
|
||||
Message = "Received untracked request result ~tp: ~tp",
|
||||
Message = "Received untracked request result ~160tp: ~160tp",
|
||||
ok = log(warning, Message, [ID, Result]),
|
||||
Dropped
|
||||
end.
|
||||
@ -1080,49 +1006,38 @@ eval_queue(State = #s{actions = Actions}) ->
|
||||
eval_queue([], State) ->
|
||||
State;
|
||||
eval_queue([Action = {request, Pid, ID, Message} | Rest],
|
||||
State = #s{actions = Actions, requests = Requests, mx = MX, cx = CX}) ->
|
||||
{NewActions, NewRequests, NewMX, NewCX} =
|
||||
State = #s{actions = Actions, requests = Requests, cx = CX}) ->
|
||||
{NewActions, NewRequests, NewCX} =
|
||||
case dispatch_request(Message, ID, CX) of
|
||||
{dispatched, NextCX} ->
|
||||
NextRequests = maps:put(ID, {Pid, Message}, Requests),
|
||||
NextMX = mx_add_monitor(Pid, requestor, MX),
|
||||
{Actions, NextRequests, NextMX, NextCX};
|
||||
{result, Response} ->
|
||||
Pid ! Response,
|
||||
{Actions, Requests, MX, CX};
|
||||
{Actions, NextRequests, NextCX};
|
||||
wait ->
|
||||
NextActions = [Action | Actions],
|
||||
NextMX = mx_add_monitor(Pid, requestor, MX),
|
||||
{NextActions, Requests, NextMX, CX}
|
||||
{NextActions, Requests, CX};
|
||||
Result ->
|
||||
Pid ! Result,
|
||||
{Actions, Requests, CX}
|
||||
end,
|
||||
NewState =
|
||||
State#s{actions = NewActions,
|
||||
requests = NewRequests,
|
||||
mx = NewMX,
|
||||
cx = NewCX},
|
||||
NewState = State#s{actions = NewActions, requests = NewRequests, cx = NewCX},
|
||||
eval_queue(Rest, NewState);
|
||||
eval_queue([Action = {subscribe, Pid, Package} | Rest],
|
||||
State = #s{actions = Actions, mx = MX, cx = CX}) ->
|
||||
{NewActions, NewMX, NewCX} =
|
||||
State = #s{actions = Actions, cx = CX}) ->
|
||||
{NewActions, NewCX} =
|
||||
case cx_add_sub(Pid, Package, CX) of
|
||||
{need_sub, Conn, NextCX} ->
|
||||
ok = zx_conn:subscribe(Conn, Package),
|
||||
NextMX = mx_add_monitor(Pid, subscriber, MX),
|
||||
{Actions, NextMX, NextCX};
|
||||
{Actions, NextCX};
|
||||
{have_sub, NextCX} ->
|
||||
NextMX = mx_add_monitor(Pid, subscriber, MX),
|
||||
{Actions, NextMX, NextCX};
|
||||
{Actions, NextCX};
|
||||
unassigned ->
|
||||
NextMX = mx_add_monitor(Pid, subscriber, MX),
|
||||
{[Action | Actions], NextMX, CX};
|
||||
{[Action | Actions], CX};
|
||||
unconfigured ->
|
||||
Pid ! {z_sub, Package, {error, bad_realm}},
|
||||
{Actions, MX, CX}
|
||||
{Actions, CX}
|
||||
end,
|
||||
eval_queue(Rest, State#s{actions = NewActions, mx = NewMX, cx = NewCX});
|
||||
eval_queue([{unsubscribe, Pid, Package} | Rest],
|
||||
State = #s{mx = MX, cx = CX}) ->
|
||||
{ok, NewMX} = mx_del_monitor(Pid, {subscription, Package}, MX),
|
||||
eval_queue(Rest, State#s{actions = NewActions, cx = NewCX});
|
||||
eval_queue([{unsubscribe, Pid, Package} | Rest], State = #s{cx = CX}) ->
|
||||
NewCX =
|
||||
case cx_del_sub(Pid, Package, CX) of
|
||||
{{drop_sub, ConnPid}, NextCX} ->
|
||||
@ -1133,11 +1048,11 @@ eval_queue([{unsubscribe, Pid, Package} | Rest],
|
||||
unassigned ->
|
||||
CX;
|
||||
unconfigured ->
|
||||
Message = "Received 'unsubscribe' request for unconfigured realm: ~tp",
|
||||
ok = log(warning, Message, [Package]),
|
||||
M = "Received 'unsubscribe' request for unconfigured realm: ~160tp",
|
||||
ok = log(warning, M, [Package]),
|
||||
CX
|
||||
end,
|
||||
eval_queue(Rest, State#s{mx = NewMX, cx = NewCX}).
|
||||
eval_queue(Rest, State#s{cx = NewCX}).
|
||||
|
||||
|
||||
-spec dispatch_request(Action, ID, CX) -> Result
|
||||
@ -1194,7 +1109,7 @@ clear_monitor(Pid,
|
||||
disconnected(Pid, State#s{mx = NewMX});
|
||||
{{Reqs, Subs}, NewMX} ->
|
||||
NewActions = drop_actions(Pid, Actions),
|
||||
{NewDropped, NewRequests} = drop_requests(Pid, Dropped, Requests),
|
||||
{NewDropped, NewRequests} = drop_requests(Reqs, Dropped, Requests),
|
||||
NewCX = cx_clear_client(Pid, Reqs, Subs, CX),
|
||||
State#s{actions = NewActions,
|
||||
requests = NewRequests,
|
||||
@ -1203,7 +1118,7 @@ clear_monitor(Pid,
|
||||
cx = NewCX};
|
||||
unknown ->
|
||||
Unexpected = {'DOWN', Ref, process, Pid, Reason},
|
||||
ok = log(warning, "Unexpected info: ~tp", [Unexpected]),
|
||||
ok = log(warning, "Unexpected info: ~160tp", [Unexpected]),
|
||||
State
|
||||
end.
|
||||
|
||||
@ -1237,7 +1152,7 @@ drop_requests(ReqIDs, Dropped, Requests) ->
|
||||
NewDrop = maps:put(K, V, Drop),
|
||||
{NewDrop, NewKeep}
|
||||
end,
|
||||
lists:fold(Partition, {Dropped, Requests}, ReqIDs).
|
||||
lists:foldl(Partition, {Dropped, Requests}, ReqIDs).
|
||||
|
||||
|
||||
-spec do_fetch(PackageID, Requestor, State) -> NewState
|
||||
@ -1252,14 +1167,8 @@ do_fetch(PackageID, Requestor, State = #s{id = ID}) ->
|
||||
Path = zx_lib:zsp_path(PackageID),
|
||||
case file:read_file(Path) of
|
||||
{ok, Bin} ->
|
||||
case do_import_package(Bin) of
|
||||
ok ->
|
||||
Requestor ! {result, ID, ok},
|
||||
{ok, State};
|
||||
Error ->
|
||||
Requestor ! {result, ID, Error},
|
||||
{ok, State}
|
||||
end;
|
||||
ok = do_fetch2(Bin, Requestor, ID),
|
||||
{ok, State};
|
||||
{error, enoent} ->
|
||||
{Realm, Name, Version} = PackageID,
|
||||
Action = {fetch, Realm, Name, Version},
|
||||
@ -1268,6 +1177,15 @@ do_fetch(PackageID, Requestor, State = #s{id = ID}) ->
|
||||
Requestor ! {result, ID, Error}
|
||||
end.
|
||||
|
||||
do_fetch2(Bin, Requestor, ID) ->
|
||||
Result =
|
||||
case do_import_package(Bin) of
|
||||
ok -> done;
|
||||
Error -> Error
|
||||
end,
|
||||
Requestor ! {result, ID, Result},
|
||||
ok.
|
||||
|
||||
|
||||
-spec do_import_zsp(file:filename()) -> zx:outcome().
|
||||
%% @private
|
||||
@ -1276,15 +1194,9 @@ do_fetch(PackageID, Requestor, State = #s{id = ID}) ->
|
||||
%% madness with a "try++" here by spawning a suicidal helper.
|
||||
|
||||
do_import_zsp(Path) ->
|
||||
{Pid, Mon} = spawn_monitor(fun() -> import_from_path(Path) end),
|
||||
receive
|
||||
{Pid, Outcome} ->
|
||||
true = demonitor(Mon, [flush]),
|
||||
Outcome;
|
||||
{'DOWN', Pid, process, Mon, Info} ->
|
||||
{error, Info}
|
||||
after 5000 ->
|
||||
{error, timeout}
|
||||
case file:read_file(Path) of
|
||||
{ok, Bin} -> do_import_package(Bin);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
|
||||
@ -1301,8 +1213,7 @@ do_import_package(Bin) ->
|
||||
end.
|
||||
|
||||
|
||||
-spec import_from_path(ZspPath) -> no_return()
|
||||
when ZspPath :: file:filename().
|
||||
-spec import_package(binary()) -> no_return().
|
||||
%% @private
|
||||
%% The happy path of .zsp installation.
|
||||
%% Must NEVER be executed by the zx_daemon directly.
|
||||
@ -1328,13 +1239,9 @@ do_import_package(Bin) ->
|
||||
%% If a place to get more fancy with the phases becomes really obvious after writing
|
||||
%% identicalish segements of functions a few places then I'll break things apart.
|
||||
|
||||
import_from_path(ZspPath) ->
|
||||
{ok, Bin} = file:read_file(ZspPath),
|
||||
import_package(Bin).
|
||||
|
||||
import_package(Bin = <<Size:24, Sig:Size/binary, Signed/binary>>) ->
|
||||
<<MetaSize:16, MetaBin:MetaSize/binary, TarGZ/binary>> = Signed,
|
||||
{PackageID, SigKeyName, _} = zx_lib:b_to_ts(MetaBin),
|
||||
{ok, {PackageID, SigKeyName, _, _}} = zx_lib:b_to_ts(MetaBin),
|
||||
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
|
||||
true = zx_key:verify(Signed, Sig, PubKey),
|
||||
ok = file:write_file(zx_lib:zsp_path(PackageID), Bin),
|
||||
@ -1346,6 +1253,35 @@ import_package(Bin = <<Size:24, Sig:Size/binary, Signed/binary>>) ->
|
||||
zx_daemon ! {self(), Result}.
|
||||
|
||||
|
||||
-spec do_build(zx:package_id()) -> zx:outcome().
|
||||
%% @private
|
||||
%% Build a project from source.
|
||||
|
||||
do_build(PackageID) ->
|
||||
{Pid, Mon} = spawn_monitor(fun() -> make(PackageID) end),
|
||||
receive
|
||||
{Pid, Outcome} ->
|
||||
true = demonitor(Mon, [flush]),
|
||||
Outcome;
|
||||
{'DOWN', Pid, process, Mon, Info} ->
|
||||
{error, Info}
|
||||
after 5000 ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
-spec make(zx:package_id()) -> no_return().
|
||||
%% @private
|
||||
%% Keep (the highly uncertain) build procedure separate from the zx_daemon, but
|
||||
%% still sequentialized by it.
|
||||
|
||||
make(PackageID) ->
|
||||
Dirs = [zx_lib:ppath(D, PackageID) || D <- [etc, var, tmp, log, lib]],
|
||||
ok = lists:foreach(fun zx_lib:force_dir/1, Dirs),
|
||||
ok = file:set_cwd(zx_lib:ppath(lib, PackageID)),
|
||||
Result = zx_lib:build(),
|
||||
zx_daemon ! {self(), Result}.
|
||||
|
||||
|
||||
%%% Monitor Index ADT Interface Functions
|
||||
|
||||
@ -1359,29 +1295,29 @@ mx_new() ->
|
||||
|
||||
-spec mx_add_monitor(Pid, Category, MX) -> NewMX
|
||||
when Pid :: pid(),
|
||||
Category :: subscriber
|
||||
| requestor
|
||||
Category :: {requestor, id()}
|
||||
| {subscriber, Sub :: tuple()}
|
||||
| attempt,
|
||||
MX :: monitor_index(),
|
||||
NewMX :: monitor_index().
|
||||
%% @private
|
||||
%% Begin monitoring the given Pid, keeping track of its category.
|
||||
|
||||
mx_add_monitor(Pid, subscriber, MX) ->
|
||||
mx_add_monitor(Pid, {subscriber, Sub}, MX) ->
|
||||
case maps:take(Pid, MX) of
|
||||
{{Ref, {Subs, Reqs}}, NextMX} ->
|
||||
maps:put(Pid, {Ref, {Subs + 1, Reqs}}, NextMX);
|
||||
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||
maps:put(Pid, {Ref, {Reqs, [Sub | Subs]}}, NextMX);
|
||||
error ->
|
||||
Ref = monitor(process, Pid),
|
||||
maps:put(Pid, {Ref, {1, 0}}, MX)
|
||||
maps:put(Pid, {Ref, {[], [Sub]}}, MX)
|
||||
end;
|
||||
mx_add_monitor(Pid, requestor, MX) ->
|
||||
mx_add_monitor(Pid, {requestor, Req}, MX) ->
|
||||
case maps:take(Pid, MX) of
|
||||
{{Ref, {Subs, Reqs}}, NextMX} ->
|
||||
maps:put(Pid, {Ref, {Subs, Reqs + 1}}, NextMX);
|
||||
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||
maps:put(Pid, {Ref, {[Req | Reqs], Subs}}, NextMX);
|
||||
error ->
|
||||
Ref = monitor(process, Pid),
|
||||
maps:put(Pid, {Ref, {0, 1}}, MX)
|
||||
maps:put(Pid, {Ref, {[Req], []}}, MX)
|
||||
end;
|
||||
mx_add_monitor(Pid, attempt, MX) ->
|
||||
false = maps:is_key(Pid, MX),
|
||||
@ -1422,13 +1358,13 @@ mx_del_monitor(Pid, conn, MX) ->
|
||||
{{Ref, conn}, NewMX} = maps:take(Pid, MX),
|
||||
true = demonitor(Ref, [flush]),
|
||||
NewMX;
|
||||
mx_del_monitor(Pid, {requestor, ID}, MX) ->
|
||||
mx_del_monitor(Pid, {requestor, Req}, MX) ->
|
||||
case maps:take(Pid, MX) of
|
||||
{{Ref, {[ID], []}}, NextMX} ->
|
||||
{{Ref, {[Req], []}}, NextMX} ->
|
||||
true = demonitor(Ref, [flush]),
|
||||
NextMX;
|
||||
{{Ref, {Reqs, Subs}}, NextMX} when Reqs > 0 ->
|
||||
NewReqs = lists:delete(ID, Reqs),
|
||||
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||
NewReqs = lists:delete(Req, Reqs),
|
||||
maps:put(Pid, {Ref, {NewReqs, Subs}}, NextMX)
|
||||
end;
|
||||
mx_del_monitor(Pid, {subscriber, Sub}, MX) ->
|
||||
@ -1436,7 +1372,7 @@ mx_del_monitor(Pid, {subscriber, Sub}, MX) ->
|
||||
{{Ref, {[], [Sub]}}, NextMX} ->
|
||||
true = demonitor(Ref, [flush]),
|
||||
NextMX;
|
||||
{{Ref, {Reqs, Subs}}, NextMX} when Subs > 0 ->
|
||||
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||
NewSubs = lists:delete(Sub, Subs),
|
||||
maps:put(Pid, {Ref, {Reqs, NewSubs}}, NextMX)
|
||||
end.
|
||||
@ -1484,7 +1420,7 @@ cx_load() ->
|
||||
{ok, Realms} ->
|
||||
#cx{realms = maps:from_list(Realms)};
|
||||
{error, Reason} ->
|
||||
Message = "Realm data and host cache load failed with : ~tp",
|
||||
Message = "Realm data and host cache load failed with : ~160tp",
|
||||
ok = log(error, Message, [Reason]),
|
||||
ok = log(warning, "No realms configured."),
|
||||
#cx{}
|
||||
@ -1524,7 +1460,7 @@ cx_populate(Realm, CX) ->
|
||||
Record = cx_load_realm_meta(Meta),
|
||||
[{Realm, Record} | CX];
|
||||
{error, Reason} ->
|
||||
Message = "Loading realm ~tp failed with: ~tp. Skipping...",
|
||||
Message = "Loading realm ~160tp failed with: ~160tp. Skipping...",
|
||||
ok = log(warning, Message, [Realm, Reason]),
|
||||
CX
|
||||
end.
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([ensure_keypair/1, have_key/2, path/2,
|
||||
prompt_keygen/0, generate_rsa/1,
|
||||
prompt_keygen/1, generate_rsa/1,
|
||||
load/2, verify/3]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
@ -30,15 +30,15 @@ ensure_keypair(KeyID = {Realm, KeyName}) ->
|
||||
{true, true} ->
|
||||
true;
|
||||
{false, true} ->
|
||||
Format = "Public key ~tp/~tp cannot be found",
|
||||
Format = "Public key ~ts/~ts cannot be found",
|
||||
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||
{error, Message, 2};
|
||||
{true, false} ->
|
||||
Format = "Private key ~tp/~tp cannot be found",
|
||||
Format = "Private key ~ts/~ts cannot be found",
|
||||
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||
{error, Message, 2};
|
||||
{false, false} ->
|
||||
Format = "Key pair ~tp/~tp cannot be found",
|
||||
Format = "Key pair ~ts/~ts cannot be found",
|
||||
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||
{error, Message, 2}
|
||||
end.
|
||||
@ -70,11 +70,11 @@ path(private, {Realm, KeyName}) ->
|
||||
|
||||
%%% Key generation
|
||||
|
||||
-spec prompt_keygen() -> zx:key_id().
|
||||
-spec prompt_keygen(zx:user_id()) -> zx:key_name().
|
||||
%% @private
|
||||
%% Prompt the user for a valid KeyPrefix to use for naming a new RSA keypair.
|
||||
|
||||
prompt_keygen() ->
|
||||
prompt_keygen(UserID = {Realm, UserName}) ->
|
||||
Message =
|
||||
"~nKEY NAME~n"
|
||||
"Enter a name for your new key pair.~n"
|
||||
@ -83,13 +83,15 @@ prompt_keygen() ->
|
||||
"consecutive underscores. (That is: [a-z0-9_])~n"
|
||||
" Example: my_key~n",
|
||||
ok = io:format(Message),
|
||||
Input = zx_tty:get_input(),
|
||||
case zx_lib:valid_lower0_9(Input) of
|
||||
KeyTag = zx_tty:get_input(),
|
||||
case zx_lib:valid_lower0_9(KeyTag) of
|
||||
true ->
|
||||
Input;
|
||||
KeyName = UserName ++ "-" ++ KeyTag,
|
||||
ok = zx_key:generate_rsa({Realm, KeyName}),
|
||||
KeyName;
|
||||
false ->
|
||||
ok = io:format("Bad key name ~tp. Try again.~n", [Input]),
|
||||
prompt_keygen()
|
||||
ok = io:format("Bad key name ~ts. Try again.~n", [KeyTag]),
|
||||
prompt_keygen(UserID)
|
||||
end.
|
||||
|
||||
|
||||
@ -117,7 +119,7 @@ generate_rsa(KeyID, BaseName) ->
|
||||
PemFile = BaseName ++ ".pub.pem",
|
||||
KeyFile = path(private, KeyID),
|
||||
PubFile = path(public, KeyID),
|
||||
ok = log(info, "Generating ~p and ~p. Please be patient...", [KeyFile, PubFile]),
|
||||
ok = log(info, "Generating ~s and ~s. Please be patient...", [KeyFile, PubFile]),
|
||||
case gen_p_key(KeyFile) of
|
||||
ok ->
|
||||
ok = der_to_pem(KeyFile, PemFile),
|
||||
@ -252,7 +254,6 @@ load(Type, KeyID) ->
|
||||
public -> 'RSAPublicKey'
|
||||
end,
|
||||
Path = path(Type, KeyID),
|
||||
ok = log(info, "Loading key from file ~ts", [Path]),
|
||||
case file:read_file(Path) of
|
||||
{ok, Bin} -> {ok, public_key:der_decode(DerType, Bin)};
|
||||
Error -> Error
|
||||
|
||||
@ -25,16 +25,19 @@
|
||||
valid_lower0_9/1, valid_label/1, valid_version/1,
|
||||
string_to_version/1, version_to_string/1,
|
||||
package_id/1, package_string/1,
|
||||
namify_zsp/1, zsp_path/1,
|
||||
zsp_name/1, zsp_path/1,
|
||||
find_latest_compatible/2, installed/1,
|
||||
realm_conf/1, load_realm_conf/1,
|
||||
build/0,
|
||||
rm_rf/1, rm/1,
|
||||
time_diff/2, elapsed_time/1,
|
||||
b_to_t/1, b_to_ts/1]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
|
||||
|
||||
-type core_dir() :: etc | var | tmp | log | key | zsp | lib.
|
||||
|
||||
|
||||
%%% Functions
|
||||
|
||||
@ -70,13 +73,7 @@ find_zomp_dir() ->
|
||||
end.
|
||||
|
||||
|
||||
-spec path(Type) -> Path
|
||||
when Type :: etc
|
||||
| var
|
||||
| tmp
|
||||
| log
|
||||
| lib,
|
||||
Path :: file:filename().
|
||||
-spec path(core_dir()) -> file:filename().
|
||||
%% @private
|
||||
%% Return the top-level path of the given type in the Zomp/ZX system.
|
||||
|
||||
@ -89,14 +86,7 @@ path(zsp) -> filename:join(zomp_dir(), "zsp");
|
||||
path(lib) -> filename:join(zomp_dir(), "lib").
|
||||
|
||||
|
||||
-spec path(Type, Realm) -> Path
|
||||
when Type :: etc
|
||||
| var
|
||||
| tmp
|
||||
| log
|
||||
| lib,
|
||||
Realm :: zx:realm(),
|
||||
Path :: file:filename().
|
||||
-spec path(core_dir(), zx:realm()) -> file:filename().
|
||||
%% @private
|
||||
%% Return the realm-level path of the given type in the Zomp/ZX system.
|
||||
|
||||
@ -104,15 +94,7 @@ path(Type, Realm) ->
|
||||
filename:join(path(Type), Realm).
|
||||
|
||||
|
||||
-spec path(Type, Realm, Name) -> Path
|
||||
when Type :: etc
|
||||
| var
|
||||
| tmp
|
||||
| log
|
||||
| lib,
|
||||
Realm :: zx:realm(),
|
||||
Name :: zx:name(),
|
||||
Path :: file:filename().
|
||||
-spec path(core_dir(), zx:realm(), zx:name()) -> file:filename().
|
||||
%% @private
|
||||
%% Return the package-level path of the given type in the Zomp/ZX system.
|
||||
|
||||
@ -120,16 +102,7 @@ path(Type, Realm, Name) ->
|
||||
filename:join([path(Type), Realm, Name]).
|
||||
|
||||
|
||||
-spec path(Type, Realm, Name, Version) -> Path
|
||||
when Type :: etc
|
||||
| var
|
||||
| tmp
|
||||
| log
|
||||
| lib,
|
||||
Realm :: zx:realm(),
|
||||
Name :: zx:name(),
|
||||
Version :: zx:version(),
|
||||
Path :: file:filename().
|
||||
-spec path(core_dir(), zx:realm(), zx:name(), zx:version()) -> file:filename().
|
||||
%% @private
|
||||
%% Return the version-specific level path of the given type in the Zomp/ZX system.
|
||||
|
||||
@ -138,14 +111,7 @@ path(Type, Realm, Name, Version) ->
|
||||
filename:join([path(Type), Realm, Name, VersionString]).
|
||||
|
||||
|
||||
-spec ppath(Type, PackageID) -> Path
|
||||
when Type :: etc
|
||||
| var
|
||||
| tmp
|
||||
| log
|
||||
| lib,
|
||||
PackageID :: zx:package_id(),
|
||||
Path :: file:filename().
|
||||
-spec ppath(core_dir(), zx:package_id()) -> file:filename().
|
||||
%% @private
|
||||
%% An alias for path/4, but more convenient when needing a path from a closed
|
||||
%% package_id().
|
||||
@ -257,7 +223,7 @@ read_project_meta(Dir) ->
|
||||
{error, enoent} ->
|
||||
{error, "No project zomp.meta file. Wrong directory? Not initialized?", 2};
|
||||
Error ->
|
||||
ok = log(error, "Read from zomp.meta failed with: ~tp", [Error]),
|
||||
ok = log(error, "Read from zomp.meta failed with: ~tw", [Error]),
|
||||
Error
|
||||
end.
|
||||
|
||||
@ -327,7 +293,7 @@ exec_shell(CMD) ->
|
||||
ok;
|
||||
Out ->
|
||||
Trimmed = string:trim(Out, trailing, "\r\n"),
|
||||
log(info, "os:cmd(~tp) -> ~ts", [CMD, Trimmed])
|
||||
log(info, "os:cmd(~tw) -> ~ts", [CMD, Trimmed])
|
||||
end.
|
||||
|
||||
|
||||
@ -335,8 +301,7 @@ exec_shell(CMD) ->
|
||||
%% @private
|
||||
%% Check whether a provided string is a valid lower0_9.
|
||||
|
||||
valid_lower0_9([Char | Rest])
|
||||
when $a =< Char, Char =< $z ->
|
||||
valid_lower0_9([Char | Rest]) when $a =< Char, Char =< $z ->
|
||||
valid_lower0_9(Rest, Char);
|
||||
valid_lower0_9(_) ->
|
||||
false.
|
||||
@ -363,8 +328,7 @@ valid_lower0_9(_, _) ->
|
||||
%% @private
|
||||
%% Check whether a provided string is a valid label.
|
||||
|
||||
valid_label([Char | Rest])
|
||||
when $a =< Char, Char =< $z ->
|
||||
valid_label([Char | Rest]) when $a =< Char, Char =< $z ->
|
||||
valid_label(Rest, Char);
|
||||
valid_label(_) ->
|
||||
false.
|
||||
@ -582,13 +546,13 @@ package_string(_) ->
|
||||
{error, invalid_package_id}.
|
||||
|
||||
|
||||
-spec namify_zsp(PackageID) -> ZrpFileName
|
||||
-spec zsp_name(PackageID) -> ZrpFileName
|
||||
when PackageID :: zx:package_id(),
|
||||
ZrpFileName :: file:filename().
|
||||
%% @private
|
||||
%% Map a PackageID to its correct .zsp package file name.
|
||||
|
||||
namify_zsp(PackageID) ->
|
||||
zsp_name(PackageID) ->
|
||||
{ok, PackageString} = package_string(PackageID),
|
||||
PackageString ++ ".zsp".
|
||||
|
||||
@ -596,7 +560,7 @@ namify_zsp(PackageID) ->
|
||||
-spec zsp_path(zx:package_id()) -> file:filename().
|
||||
|
||||
zsp_path(PackageID = {Realm, _, _}) ->
|
||||
filename:join(path(zsp, Realm), namify_zsp(PackageID)).
|
||||
filename:join(path(zsp, Realm), zsp_name(PackageID)).
|
||||
|
||||
|
||||
-spec find_latest_compatible(Version, Versions) -> Result
|
||||
@ -675,7 +639,7 @@ load_realm_conf(Realm) ->
|
||||
{ok, C} ->
|
||||
{ok, maps:from_list(C)};
|
||||
Error ->
|
||||
ok = log(warning, "Loading realm conf ~ts failed with: ~tp", [Path, Error]),
|
||||
ok = log(warning, "Loading realm conf ~ts failed with: ~tw", [Path, Error]),
|
||||
Error
|
||||
end.
|
||||
|
||||
@ -732,6 +696,25 @@ rm(Path) ->
|
||||
end.
|
||||
|
||||
|
||||
-spec time_diff(Before, After) -> Diff
|
||||
when Before :: calendar:datetime(),
|
||||
After :: calendar:datetime(),
|
||||
Diff :: integer().
|
||||
|
||||
time_diff(Before, After) ->
|
||||
Early = calendar:datetime_to_gregorian_seconds(Before),
|
||||
Late = calendar:datetime_to_gregorian_seconds(After),
|
||||
Late - Early.
|
||||
|
||||
|
||||
-spec elapsed_time(Timestamp) -> Diff
|
||||
when Timestamp :: calendar:datetime(),
|
||||
Diff :: integer().
|
||||
|
||||
elapsed_time(Timestamp) ->
|
||||
time_diff(Timestamp, calendar:universal_time()).
|
||||
|
||||
|
||||
-spec b_to_t(binary()) -> {ok, term()} | error.
|
||||
%% @private
|
||||
%% A wrapper for the binary_to_term/1 BIF to hide the try..catch mess in the places we
|
||||
@ -752,7 +735,7 @@ b_to_t(Binary) ->
|
||||
|
||||
b_to_ts(Binary) ->
|
||||
try
|
||||
binary_to_term(Binary, [safe])
|
||||
{ok, binary_to_term(Binary, [safe])}
|
||||
catch
|
||||
error:badarg -> error
|
||||
end.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -9,11 +9,48 @@
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([disconnect/1]).
|
||||
-export([peername/1,
|
||||
host_string/1,
|
||||
disconnect/1,
|
||||
tx/2, tx/3,
|
||||
rx/1, rx/2, rx/3,
|
||||
err_ex/1, err_in/1]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
|
||||
|
||||
|
||||
%%% Library Functions
|
||||
|
||||
-spec peername(Socket) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Result :: {ok, zx:host()}
|
||||
| {error, inet:posix()}.
|
||||
%% @doc
|
||||
%% Returns the IPv4 or IPv6 peer-side address of a connected socket, translating the
|
||||
%% address to IPv4 in the case that it is a translated IPv4 -> IPv6 address.
|
||||
|
||||
peername(Socket) ->
|
||||
case inet:peername(Socket) of
|
||||
{ok, {{0, 0, 0, 0, 0, 65535, X, Y}, Port}} ->
|
||||
<<A:8, B:8, C:8, D:8>> = <<X:16, Y:16>>,
|
||||
{ok, {{A, B, C, D}, Port}};
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
|
||||
-spec host_string(zx:host()) -> string().
|
||||
|
||||
host_string({Address, Port}) when is_list(Address) ->
|
||||
PortS = integer_to_list(Port),
|
||||
Address ++ ":" ++ PortS;
|
||||
host_string({Address, Port}) ->
|
||||
AddressS = inet:ntoa(Address),
|
||||
PortS = integer_to_list(Port),
|
||||
AddressS ++ ":" ++ PortS.
|
||||
|
||||
|
||||
-spec disconnect(Socket) -> ok
|
||||
when Socket :: gen_tcp:socket().
|
||||
%% @doc
|
||||
@ -21,12 +58,12 @@
|
||||
%% disconnected on the other side.
|
||||
|
||||
disconnect(Socket) ->
|
||||
case zomp:peername(Socket) of
|
||||
case peername(Socket) of
|
||||
{ok, {Addr, Port}} ->
|
||||
Host = inet:ntoa(Addr),
|
||||
disconnect(Socket, Host, Port);
|
||||
{error, Reason} ->
|
||||
log(warning, "Disconnect failed with: ~p", [Reason])
|
||||
log(warning, "Disconnect failed with: ~w", [Reason])
|
||||
end.
|
||||
|
||||
|
||||
@ -38,17 +75,182 @@ disconnect(Socket) ->
|
||||
disconnect(Socket, Host, Port) ->
|
||||
case gen_tcp:shutdown(Socket, read_write) of
|
||||
ok ->
|
||||
log(info, "~ts:~w disconnected", [Host, Port]);
|
||||
ok;
|
||||
{error, enotconn} ->
|
||||
log(info, "~ts:~w disconnected", [Host, Port]),
|
||||
receive
|
||||
{tcp_closed, Socket} -> ok
|
||||
after 0 -> ok
|
||||
end;
|
||||
{error, E} ->
|
||||
log(warning, "~ts:~w disconnect failed with: ~p", [Host, Port, E]),
|
||||
log(warning, "~ts:~w disconnect failed with: ~w", [Host, Port, E]),
|
||||
receive
|
||||
{tcp_closed, Socket} -> ok
|
||||
after 0 -> ok
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
-spec tx(Socket, Bytes) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Bytes :: iodata(),
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: tcp_closed
|
||||
| timeout
|
||||
| inet:posix().
|
||||
|
||||
tx(Socket, Bytes) ->
|
||||
tx(Socket, Bytes, 5000).
|
||||
|
||||
|
||||
-spec tx(Socket, Bytes, Timeout) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Bytes :: iodata(),
|
||||
Timeout :: pos_integer(),
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: tcp_closed
|
||||
| timeout
|
||||
| inet:posix().
|
||||
|
||||
tx(Socket, Bytes, Timeout) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<0:8>>} -> tx2(Socket, Bytes, Timeout);
|
||||
{tcp, Socket, Bin} -> {error, Bin};
|
||||
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||
after Timeout -> {error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
tx2(Socket, Bytes, Timeout) ->
|
||||
case gen_tcp:send(Socket, Bytes) of
|
||||
ok ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<0:8>>} -> ok;
|
||||
{tcp, Socket, Bin} -> {error, Bin};
|
||||
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||
after Timeout -> {error, timeout}
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
-spec rx(Socket) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Result :: {ok, binary()}
|
||||
| {error, timeout | tcp_closed}.
|
||||
%% @doc
|
||||
%% Abstract large receives with a fixed timeout of 5 seconds between segments.
|
||||
|
||||
rx(Socket) ->
|
||||
rx(Socket, 5000, []).
|
||||
|
||||
|
||||
-spec rx(Socket, Timeout) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Timeout :: pos_integer(),
|
||||
Result :: {ok, binary()}
|
||||
| {error, timeout | tcp_closed}.
|
||||
%% @doc
|
||||
%% Abstract large receives with a fixed timeout between segments.
|
||||
|
||||
rx(Socket, Timeout) ->
|
||||
rx(Socket, Timeout, []).
|
||||
|
||||
|
||||
-spec rx(Socket, Timeout, Watchers) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Timeout :: pos_integer(),
|
||||
Watchers :: [pid()],
|
||||
Result :: {ok, binary()}
|
||||
| {error, timeout | tcp_closed}.
|
||||
%% @doc
|
||||
%% Abstract large receives with a fixed timeout between segments and progress update
|
||||
%% messages to listening processes.
|
||||
|
||||
rx(Socket, Timeout, Watchers) ->
|
||||
ok = inet:setopts(Socket, [{active, once}, {packet, 0}]),
|
||||
ok = gen_tcp:send(Socket, <<1:32, 0:8>>),
|
||||
receive
|
||||
{tcp, Socket, <<Size:32, Bin/binary>>} ->
|
||||
ok = broadcast({rx, Socket, start, Size}, Watchers),
|
||||
Left = Size - byte_size(Bin),
|
||||
rx(Socket, Watchers, Left, Bin, Timeout);
|
||||
{tcp_closed, Socket} ->
|
||||
{error, tcp_closed}
|
||||
after Timeout ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
rx(Socket, Watchers, 0, Bin, _) ->
|
||||
ok = inet:setopts(Socket, [{packet, 4}]),
|
||||
ok = gen_tcp:send(Socket, <<0:8>>),
|
||||
ok = broadcast({rx, Socket, done}, Watchers),
|
||||
{ok, Bin};
|
||||
rx(Socket, Watchers, Left, Buffer, Timeout) when Left > 0 ->
|
||||
ok = broadcast({rx, Socket, pending, Left}, Watchers),
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, Bin} ->
|
||||
NewLeft = Left - byte_size(Bin),
|
||||
rx(Socket, Watchers, NewLeft, <<Buffer/binary, Bin/binary>>, Timeout);
|
||||
{tcp_closed, Socket} ->
|
||||
{error, tcp_closed}
|
||||
after Timeout ->
|
||||
{error, timeout}
|
||||
end;
|
||||
rx(Socket, Watchers, Left, _, _) when Left < 0 ->
|
||||
ok = broadcast({rx, Socket, overflow}, Watchers),
|
||||
{error, bad_message}.
|
||||
|
||||
|
||||
broadcast(Message, Pids) ->
|
||||
Notify = fun(P) -> P ! Message end,
|
||||
lists:foreach(Notify, Pids).
|
||||
|
||||
|
||||
err_ex(bad_message) -> <<0:1, 1:7>>;
|
||||
err_ex(timeout) -> <<0:1, 2:7>>;
|
||||
err_ex(bad_realm) -> <<0:1, 3:7>>;
|
||||
err_ex(bad_package) -> <<0:1, 4:7>>;
|
||||
err_ex(bad_version) -> <<0:1, 5:7>>;
|
||||
err_ex(bad_serial) -> <<0:1, 6:7>>;
|
||||
err_ex(bad_user) -> <<0:1, 7:7>>;
|
||||
err_ex(bad_key) -> <<0:1, 8:7>>;
|
||||
err_ex(bad_sig) -> <<0:1, 9:7>>;
|
||||
err_ex(no_permission) -> <<0:1, 10:7>>;
|
||||
err_ex(unknown_user) -> <<0:1, 11:7>>;
|
||||
err_ex(bad_request) -> <<0:1, 12:7>>;
|
||||
err_ex(realm_mismatch) -> <<0:1, 13:7>>;
|
||||
err_ex(not_prime) -> <<0:1, 14:7>>;
|
||||
err_ex(bad_auth) -> <<0:1, 15:7>>;
|
||||
err_ex(unauthorized_key) -> <<0:1, 16:7>>;
|
||||
err_ex(already_exists) -> <<0:1, 17:7>>;
|
||||
err_ex(busy) -> <<0:1, 18:7>>;
|
||||
err_ex({retry, Seconds}) -> <<0:1, 19:7, Seconds:24>>;
|
||||
err_ex(Reason) -> [<<0:1, 127:7>>, io_lib:format("~tw", [Reason])].
|
||||
|
||||
|
||||
err_in(<<0:1, 1:7>>) -> bad_message;
|
||||
err_in(<<0:1, 2:7>>) -> timeout;
|
||||
err_in(<<0:1, 3:7>>) -> bad_realm;
|
||||
err_in(<<0:1, 4:7>>) -> bad_package;
|
||||
err_in(<<0:1, 5:7>>) -> bad_version;
|
||||
err_in(<<0:1, 6:7>>) -> bad_serial;
|
||||
err_in(<<0:1, 7:7>>) -> bad_user;
|
||||
err_in(<<0:1, 8:7>>) -> bad_key;
|
||||
err_in(<<0:1, 9:7>>) -> bad_sig;
|
||||
err_in(<<0:1, 10:7>>) -> no_permission;
|
||||
err_in(<<0:1, 11:7>>) -> unknown_user;
|
||||
err_in(<<0:1, 12:7>>) -> bad_request;
|
||||
err_in(<<0:1, 13:7>>) -> realm_mismatch;
|
||||
err_in(<<0:1, 14:7>>) -> not_prime;
|
||||
err_in(<<0:1, 15:7>>) -> bad_auth;
|
||||
err_in(<<0:1, 16:7>>) -> unauthorized_key;
|
||||
err_in(<<0:1, 17:7>>) -> already_exists;
|
||||
err_in(<<0:1, 18:7>>) -> busy;
|
||||
err_in(<<0:1, 19:7, Seconds:24>>) -> {retry, Seconds};
|
||||
err_in(<<0:1, 127:7, Reason/binary>>) -> unicode:characters_to_list(Reason).
|
||||
|
||||
@ -62,7 +62,7 @@ load() ->
|
||||
{ok, List} ->
|
||||
populate_data(List);
|
||||
{error, Reason} ->
|
||||
ok = log(error, "Load ~tp failed with: ~tp", [Path, Reason]),
|
||||
ok = log(error, "Load ~160tp failed with: ~160tp", [Path, Reason]),
|
||||
Data = #d{},
|
||||
ok = save(Data),
|
||||
Data
|
||||
@ -203,7 +203,7 @@ maxconn(Value, Data) when is_integer(Value) and Value > 0 ->
|
||||
Data#d{maxconn = Value}.
|
||||
|
||||
|
||||
-spec managed(data()) -> list(zx:realm()).
|
||||
-spec managed(data()) -> [zx:realm()].
|
||||
%% @doc
|
||||
%% Return the list of realms managed by the current node.
|
||||
|
||||
@ -241,10 +241,10 @@ add_managed(Realm, Data = #d{managed = Managed}) ->
|
||||
case zx_lib:realm_exists(Realm) of
|
||||
true ->
|
||||
NewData = Data#d{managed = sets:add_element(Realm, Managed)},
|
||||
ok = log(info, "Now managing realm: ~tp", [Realm]),
|
||||
ok = log(info, "Now managing realm: ~160tp", [Realm]),
|
||||
{ok, NewData};
|
||||
false ->
|
||||
ok = log(warning, "Cannot manage unconfigured realm: ~tp", [Realm]),
|
||||
ok = log(warning, "Cannot manage unconfigured realm: ~160tp", [Realm]),
|
||||
{error, unconfigured}
|
||||
end.
|
||||
|
||||
@ -262,10 +262,10 @@ rem_managed(Realm, Data = #d{managed = Managed}) ->
|
||||
case sets:is_element(Realm, Managed) of
|
||||
true ->
|
||||
NewData = Data#d{managed = sets:del_element(Realm, Managed)},
|
||||
ok = log(info, "No longer managing realm: ~tp", [Realm]),
|
||||
ok = log(info, "No longer managing realm: ~160tp", [Realm]),
|
||||
{ok, NewData};
|
||||
false ->
|
||||
ok = log(warning, "Cannot stop managing unmanaged realm: ~tp", [Realm]),
|
||||
ok = log(warning, "Cannot stop managing unmanaged realm: ~160tp", [Realm]),
|
||||
{error, unmanaged}
|
||||
end.
|
||||
|
||||
|
||||
@ -10,7 +10,10 @@
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([get_input/0, get_input/1, get_input/2, select/1, select_string/1]).
|
||||
-export([get_input/0, get_input/1, get_input/2, get_input/3,
|
||||
get_lower0_9/1, get_lower0_9/2, get_lower0_9/3,
|
||||
select/1, select_string/1,
|
||||
derp/0]).
|
||||
|
||||
|
||||
%%% Type Definitions
|
||||
@ -32,12 +35,12 @@ get_input() ->
|
||||
end.
|
||||
|
||||
|
||||
-spec get_input(Prompt :: string()) -> string().
|
||||
-spec get_input(Info :: string()) -> string().
|
||||
%% @private
|
||||
%% Introduce
|
||||
%% Prompt the user for input.
|
||||
|
||||
get_input(Prompt) ->
|
||||
get_input(Prompt, []).
|
||||
get_input(Info) ->
|
||||
get_input(Info, []).
|
||||
|
||||
|
||||
-spec get_input(Format, Args) -> string()
|
||||
@ -46,14 +49,55 @@ get_input(Prompt) ->
|
||||
%% @private
|
||||
%% Allow the caller to use io format strings and args to create a prompt.
|
||||
|
||||
|
||||
get_input(Format, Args) ->
|
||||
Prompt = io_lib:format(Format, Args),
|
||||
case string:trim(io:get_line(["(or \"QUIT\") ", Prompt, ": "])) of
|
||||
get_input(Format, Args, []).
|
||||
|
||||
|
||||
-spec get_input(Format, Args, Prompt) -> string()
|
||||
when Format :: string(),
|
||||
Args :: [term()],
|
||||
Prompt :: string().
|
||||
%% @private
|
||||
%% Similar to get_input/2, but allows a default prompt to be inserted after the
|
||||
%% "QUIT" message (useful for things like bracketed default values).
|
||||
|
||||
get_input(Format, Args, Prompt) ->
|
||||
Info = io_lib:format(Format, Args),
|
||||
case string:trim(io:get_line([Info, "(or \"QUIT\") ", Prompt, ": "])) of
|
||||
"QUIT" -> what_a_quitter();
|
||||
String -> String
|
||||
end.
|
||||
|
||||
|
||||
-spec get_lower0_9(Prompt :: string()) -> zx:lower0_9().
|
||||
%% @private
|
||||
%% Prompt the user for input, constraining the input to valid zx:lower0_9 strings.
|
||||
|
||||
get_lower0_9(Prompt) ->
|
||||
get_lower0_9(Prompt, []).
|
||||
|
||||
|
||||
-spec get_lower0_9(Format, Args) -> zx:lower0_9()
|
||||
when Format :: string(),
|
||||
Args :: [term()].
|
||||
|
||||
get_lower0_9(Format, Args) ->
|
||||
get_lower0_9(Format, Args, "").
|
||||
|
||||
|
||||
get_lower0_9(Format, Args, Prompt) ->
|
||||
String = get_input(Format, Args, Prompt),
|
||||
case zx_lib:valid_lower0_9(String) of
|
||||
true ->
|
||||
String;
|
||||
false ->
|
||||
Message = "The string \"~ts\" isn't valid. Try \"[a-z0-9_]*\".~n",
|
||||
ok = io:format(Message, [String]),
|
||||
get_lower0_9(Format, Args, Prompt)
|
||||
end.
|
||||
|
||||
|
||||
-spec select(Options) -> Selected
|
||||
when Options :: [option()],
|
||||
Selected :: term().
|
||||
@ -139,3 +183,9 @@ hurr() -> io:format("That isn't an option.~n").
|
||||
what_a_quitter() ->
|
||||
ok = io:format("User abort: \"QUIT\".~nHalting.~n"),
|
||||
halt(0).
|
||||
|
||||
|
||||
-spec derp() -> ok.
|
||||
|
||||
derp() ->
|
||||
io:format("~nArglebargle, glop-glyf!?!~n~n").
|
||||
|
||||
1
zomp/lib/otpr/zx/0.1.0/templates/Emakefile
Normal file
1
zomp/lib/otpr/zx/0.1.0/templates/Emakefile
Normal file
@ -0,0 +1 @@
|
||||
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.
|
||||
22
zomp/lib/otpr/zx/0.1.0/templates/boringlib/funfile.erl
Normal file
22
zomp/lib/otpr/zx/0.1.0/templates/boringlib/funfile.erl
Normal file
@ -0,0 +1,22 @@
|
||||
%%% @doc
|
||||
%%% 〘*PROJECT NAME*〙: 〘*MODULE*〙
|
||||
%%%
|
||||
%%% This module is currently named `〘*MODULE*〙', but you may want to change that.
|
||||
%%% Remember that changing the name in `-module()' below requires renaming
|
||||
%%% this file, and it is recommended to run `zx update .app` in the main
|
||||
%%% project directory to make sure the ebin/〘*MODULE*〙.app file stays in
|
||||
%%% sync with the project whenever you add, remove or rename a module.
|
||||
%%% @end
|
||||
|
||||
-module(〘*MODULE*〙).
|
||||
〘*AUTHOR*〙
|
||||
〘*COPYRIGHT*〙
|
||||
〘*LICENSE*〙
|
||||
|
||||
-export([hello/0]).
|
||||
|
||||
|
||||
-spec hello() -> ok.
|
||||
|
||||
hello() ->
|
||||
io:format("~p (~p) says \"Hello!\"~n", [self(), ?MODULE]).
|
||||
17
zomp/lib/otpr/zx/0.1.0/templates/escript
Executable file
17
zomp/lib/otpr/zx/0.1.0/templates/escript
Executable file
@ -0,0 +1,17 @@
|
||||
#! /usr/bin/env escript
|
||||
|
||||
%%% 〘*PROJECT NAME*〙
|
||||
|
||||
-mode(compile).
|
||||
|
||||
〘*SCRIPT*〙
|
||||
〘*AUTHOR*〙
|
||||
〘*COPYRIGHT*〙
|
||||
〘*LICENSE*〙
|
||||
|
||||
-spec main(Args :: [term()]) -> no_return().
|
||||
|
||||
main(Args) ->
|
||||
ok = io:setopts([{encoding, unicode}]),
|
||||
ok = io:format("Hello, world!~n"),
|
||||
io:format("I received the args: ~tp~n", [Args]).
|
||||
75
zomp/lib/otpr/zx/0.1.0/templates/example_server/appmod.erl
Normal file
75
zomp/lib/otpr/zx/0.1.0/templates/example_server/appmod.erl
Normal file
@ -0,0 +1,75 @@
|
||||
%%% @doc
|
||||
%%% 〘*PROJECT NAME*〙
|
||||
%%% @end
|
||||
|
||||
-module(〘*APP MOD*〙).
|
||||
-behavior(application).
|
||||
〘*AUTHOR*〙
|
||||
〘*COPYRIGHT*〙
|
||||
〘*LICENSE*〙
|
||||
|
||||
-export([listen/1, ignore/0]).
|
||||
-export([start/0, start/1]).
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
|
||||
|
||||
-spec listen(PortNum) -> Result
|
||||
when PortNum :: inet:port_num(),
|
||||
Result :: ok
|
||||
| {error, {listening, inet:port_num()}}.
|
||||
%% @doc
|
||||
%% Make the server start listening on a port.
|
||||
%% Returns an {error, Reason} tuple if it is already listening.
|
||||
|
||||
listen(PortNum) ->
|
||||
〘*PREFIX*〙client_man:listen(PortNum).
|
||||
|
||||
|
||||
-spec ignore() -> ok.
|
||||
%% @doc
|
||||
%% Make the server stop listening if it is, or continue to do nothing if it isn't.
|
||||
|
||||
ignore() ->
|
||||
〘*PREFIX*〙client_man:ignore().
|
||||
|
||||
|
||||
-spec start() -> ok.
|
||||
%% @doc
|
||||
%% Start the server in an "ignore" state.
|
||||
|
||||
start() ->
|
||||
ok = application:ensure_started(sasl),
|
||||
ok = application:start(example_server),
|
||||
io:format("Starting es...").
|
||||
|
||||
|
||||
-spec start(PortNum) -> ok
|
||||
when PortNum :: inet:port_number().
|
||||
%% @doc
|
||||
%% Start the server and begin listening immediately. Slightly more convenient when
|
||||
%% playing around in the shell.
|
||||
|
||||
start(PortNum) ->
|
||||
ok = start(),
|
||||
ok = 〘*PREFIX*〙client_man:listen(PortNum),
|
||||
io:format("Startup complete, listening on ~w~n", [PortNum]).
|
||||
|
||||
|
||||
-spec start(normal, term()) -> {ok, pid()}.
|
||||
%% @private
|
||||
%% Called by OTP to kick things off. This is for the use of the "application" part of
|
||||
%% OTP, not to be called by user code.
|
||||
%% See: http://erlang.org/doc/apps/kernel/application.html
|
||||
|
||||
start(normal, _Args) ->
|
||||
〘*PREFIX*〙sup:start_link().
|
||||
|
||||
|
||||
-spec stop(term()) -> ok.
|
||||
%% @private
|
||||
%% Similar to start/2 above, this is to be called by the "application" part of OTP,
|
||||
%% not client code. Causes a (hopefully graceful) shutdown of the application.
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
203
zomp/lib/otpr/zx/0.1.0/templates/example_server/client.erl
Normal file
203
zomp/lib/otpr/zx/0.1.0/templates/example_server/client.erl
Normal file
@ -0,0 +1,203 @@
|
||||
%%% @doc
|
||||
%%% 〘*PROJECT NAME*〙 Client
|
||||
%%%
|
||||
%%% An extremely naive (currently Telnet) client handler.
|
||||
%%% Unlike other modules that represent discrete processes, this one does not adhere
|
||||
%%% to any OTP behavior. It does, however, adhere to OTP.
|
||||
%%%
|
||||
%%% In some cases it is more comfortable to write socket handlers or a certain
|
||||
%%% category of state machines as "pure" Erlang processes. This approach is made
|
||||
%%% OTP-able by use of the proc_lib module, which is the underlying library used
|
||||
%%% to write the stdlib's behaviors like gen_server, gen_statem, gen_fsm, etc.
|
||||
%%%
|
||||
%%% http://erlang.org/doc/design_principles/spec_proc.html
|
||||
%%% @end
|
||||
|
||||
-module(〘*PREFIX*〙client).
|
||||
〘*AUTHOR*〙
|
||||
〘*COPYRIGHT*〙
|
||||
〘*LICENSE*〙
|
||||
|
||||
-export([start/1]).
|
||||
-export([start_link/1, init/2]).
|
||||
-export([system_continue/3, system_terminate/4,
|
||||
system_get_state/1, system_replace_state/2]).
|
||||
|
||||
|
||||
%%% Type and Record Definitions
|
||||
|
||||
|
||||
-record(s, {socket = none :: none | gen_tcp:socket()}).
|
||||
|
||||
|
||||
%% An alias for the state record above. Aliasing state can smooth out annoyances
|
||||
%% that can arise from using the record directly as its own type all over the code.
|
||||
|
||||
-type state() :: #s{}.
|
||||
|
||||
|
||||
%%% Service Interface
|
||||
|
||||
|
||||
-spec start(ListenSocket) -> Result
|
||||
when ListenSocket :: gen_tcp:socket(),
|
||||
Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: {already_started, pid()}
|
||||
| {shutdown, term()}
|
||||
| term().
|
||||
%% @private
|
||||
%% How the 〘*PREFIX*〙client_man or a prior 〘*PREFIX*〙client kicks things off.
|
||||
%% This is called in the context of 〘*PREFIX*〙client_man or the prior 〘*PREFIX*〙client.
|
||||
|
||||
start(ListenSocket) ->
|
||||
〘*PREFIX*〙client_sup:start_acceptor(ListenSocket).
|
||||
|
||||
|
||||
-spec start_link(ListenSocket) -> Result
|
||||
when ListenSocket :: gen_tcp:socket(),
|
||||
Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: {already_started, pid()}
|
||||
| {shutdown, term()}
|
||||
| term().
|
||||
%% @private
|
||||
%% This is called by the 〘*PREFIX*〙client_sup. While start/1 is called to iniate a startup
|
||||
%% (essentially requesting a new worker be started by the supervisor), this is
|
||||
%% actually called in the context of the supervisor.
|
||||
|
||||
start_link(ListenSocket) ->
|
||||
proc_lib:start_link(?MODULE, init, [self(), ListenSocket]).
|
||||
|
||||
|
||||
-spec init(Parent, ListenSocket) -> no_return()
|
||||
when Parent :: pid(),
|
||||
ListenSocket :: gen_tcp:socket().
|
||||
%% @private
|
||||
%% This is the first code executed in the context of the new worker itself.
|
||||
%% This function does not have any return value, as the startup return is
|
||||
%% passed back to the supervisor by calling proc_lib:init_ack/2.
|
||||
%% We see the initial form of the typical arity-3 service loop form here in the
|
||||
%% call to listen/3.
|
||||
|
||||
init(Parent, ListenSocket) ->
|
||||
ok = io:format("~p Listening.~n", [self()]),
|
||||
Debug = sys:debug_options([]),
|
||||
ok = proc_lib:init_ack(Parent, {ok, self()}),
|
||||
listen(Parent, Debug, ListenSocket).
|
||||
|
||||
|
||||
-spec listen(Parent, Debug, ListenSocket) -> no_return()
|
||||
when Parent :: pid(),
|
||||
Debug :: [sys:dbg_opt()],
|
||||
ListenSocket :: gen_tcp:socket().
|
||||
%% @private
|
||||
%% This function waits for a TCP connection. The owner of the socket is still
|
||||
%% the 〘*PREFIX*〙client_man (so it can still close it on a call to 〘*PREFIX*〙client_man:ignore/0),
|
||||
%% but the only one calling gen_tcp:accept/1 on it is this process. Closing the socket
|
||||
%% is one way a manager process can gracefully unblock child workers that are blocking
|
||||
%% on a network accept.
|
||||
%%
|
||||
%% Once it makes a TCP connection it will call start/1 to spawn its successor.
|
||||
|
||||
listen(Parent, Debug, ListenSocket) ->
|
||||
case gen_tcp:accept(ListenSocket) of
|
||||
{ok, Socket} ->
|
||||
{ok, _} = start(ListenSocket),
|
||||
{ok, Peer} = inet:peername(Socket),
|
||||
ok = io:format("~p Connection accepted from: ~p~n", [self(), Peer]),
|
||||
ok = 〘*PREFIX*〙client_man:enroll(),
|
||||
State = #s{socket = Socket},
|
||||
loop(Parent, Debug, State);
|
||||
{error, closed} ->
|
||||
ok = io:format("~p Retiring: Listen socket closed.~n", [self()]),
|
||||
exit(normal)
|
||||
end.
|
||||
|
||||
|
||||
-spec loop(Parent, Debug, State) -> no_return()
|
||||
when Parent :: pid(),
|
||||
Debug :: [sys:dbg_opt()],
|
||||
State :: state().
|
||||
%% @private
|
||||
%% The service loop itself. This is the service state. The process blocks on receive
|
||||
%% of Erlang messages, TCP segments being received themselves as Erlang messages.
|
||||
|
||||
loop(Parent, Debug, State = #s{socket = Socket}) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<"bye\r\n">>} ->
|
||||
ok = io:format("~p Client saying goodbye. Bye!~n", [self()]),
|
||||
ok = gen_tcp:send(Socket, "Bye!\r\n"),
|
||||
ok = gen_tcp:shutdown(Socket, read_write),
|
||||
exit(normal);
|
||||
{tcp, Socket, Message} ->
|
||||
ok = io:format("~p received: ~tp~n", [self(), Message]),
|
||||
ok = 〘*PREFIX*〙client_man:echo(Message),
|
||||
loop(Parent, Debug, State);
|
||||
{relay, Sender, Message} when Sender == self() ->
|
||||
ok = gen_tcp:send(Socket, ["Message from YOU: ", Message]),
|
||||
loop(Parent, Debug, State);
|
||||
{relay, Sender, Message} ->
|
||||
From = io_lib:format("Message from ~tp: ", [Sender]),
|
||||
ok = gen_tcp:send(Socket, [From, Message]),
|
||||
loop(Parent, Debug, State);
|
||||
{tcp_closed, Socket} ->
|
||||
ok = io:format("~p Socket closed, retiring.~n", [self()]),
|
||||
exit(normal);
|
||||
{system, From, Request} ->
|
||||
sys:handle_system_msg(Request, From, Parent, ?MODULE, Debug, State);
|
||||
Unexpected ->
|
||||
ok = io:format("~p Unexpected message: ~tp", [self(), Unexpected]),
|
||||
loop(Parent, Debug, State)
|
||||
end.
|
||||
|
||||
|
||||
-spec system_continue(Parent, Debug, State) -> no_return()
|
||||
when Parent :: pid(),
|
||||
Debug :: [sys:dbg_opt()],
|
||||
State :: state().
|
||||
%% @private
|
||||
%% The function called by the OTP internal functions after a system message has been
|
||||
%% handled. If the worker process has several possible states this is one place
|
||||
%% resumption of a specific state can be specified and dispatched.
|
||||
|
||||
system_continue(Parent, Debug, State) ->
|
||||
loop(Parent, Debug, State).
|
||||
|
||||
|
||||
-spec system_terminate(Reason, Parent, Debug, State) -> no_return()
|
||||
when Reason :: term(),
|
||||
Parent :: pid(),
|
||||
Debug :: [sys:dbg_opt()],
|
||||
State :: state().
|
||||
%% @private
|
||||
%% Called by the OTP inner bits to allow the process to terminate gracefully.
|
||||
%% Exactly when and if this is callback gets called is specified in the docs:
|
||||
%% See: http://erlang.org/doc/design_principles/spec_proc.html#msg
|
||||
|
||||
system_terminate(Reason, _Parent, _Debug, _State) ->
|
||||
exit(Reason).
|
||||
|
||||
|
||||
|
||||
-spec system_get_state(State) -> {ok, State}
|
||||
when State :: state().
|
||||
%% @private
|
||||
%% This function allows the runtime (or anything else) to inspect the running state
|
||||
%% of the worker process at any arbitrary time.
|
||||
|
||||
system_get_state(State) -> {ok, State}.
|
||||
|
||||
|
||||
-spec system_replace_state(StateFun, State) -> {ok, NewState, State}
|
||||
when StateFun :: fun(),
|
||||
State :: state(),
|
||||
NewState :: term().
|
||||
%% @private
|
||||
%% This function allows the system to update the process state in-place. This is most
|
||||
%% useful for state transitions between code types, like when performing a hot update
|
||||
%% (very cool, but sort of hard) or hot patching a running system (living on the edge!).
|
||||
|
||||
system_replace_state(StateFun, State) ->
|
||||
{ok, StateFun(State), State}.
|
||||
296
zomp/lib/otpr/zx/0.1.0/templates/example_server/client_man.erl
Normal file
296
zomp/lib/otpr/zx/0.1.0/templates/example_server/client_man.erl
Normal file
@ -0,0 +1,296 @@
|
||||
%%% @doc
|
||||
%%% 〘*PROJECT NAME*〙 Client Manager
|
||||
%%%
|
||||
%%% This is the "manager" part of the service->worker pattern.
|
||||
%%% It keeps track of who is connected and can act as a router among the workers.
|
||||
%%% Having this process allows us to abstract and customize service-level concepts
|
||||
%%% (the high-level ideas we care about in terms of solving an external problem in the
|
||||
%%% real world) and keep them separate from the lower-level details of supervision that
|
||||
%%% OTP should take care of for us.
|
||||
%%% @end
|
||||
|
||||
-module(〘*PREFIX*〙client_man).
|
||||
-behavior(gen_server).
|
||||
〘*AUTHOR*〙
|
||||
〘*COPYRIGHT*〙
|
||||
〘*LICENSE*〙
|
||||
|
||||
-export([listen/1, ignore/0]).
|
||||
-export([enroll/0, echo/1]).
|
||||
-export([start_link/0]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
code_change/3, terminate/2]).
|
||||
|
||||
|
||||
%%% Type and Record Definitions
|
||||
|
||||
|
||||
-record(s, {port_num = none :: none | inet:port_number(),
|
||||
listener = none :: none | gen_tcp:socket(),
|
||||
clients = [] :: [pid()]}).
|
||||
|
||||
|
||||
-type state() :: #s{}.
|
||||
|
||||
|
||||
|
||||
%%% Service Interface
|
||||
|
||||
|
||||
-spec listen(PortNum) -> Result
|
||||
when PortNum :: inet:port_number(),
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: {listening, inet:port_number()}.
|
||||
%% @doc
|
||||
%% Tell the service to start listening on a given port.
|
||||
%% Only one port can be listened on at a time in the current implementation, so
|
||||
%% an error is returned if the service is already listening.
|
||||
|
||||
listen(PortNum) ->
|
||||
gen_server:call(?MODULE, {listen, PortNum}).
|
||||
|
||||
|
||||
-spec ignore() -> ok.
|
||||
%% @doc
|
||||
%% Tell the service to stop listening.
|
||||
%% It is not an error to call this function when the service is not listening.
|
||||
|
||||
ignore() ->
|
||||
gen_server:cast(?MODULE, ignore).
|
||||
|
||||
|
||||
|
||||
%%% Client Process Interface
|
||||
|
||||
|
||||
-spec enroll() -> ok.
|
||||
%% @doc
|
||||
%% Clients register here when they establish a connection.
|
||||
%% Other processes can enroll as well.
|
||||
|
||||
enroll() ->
|
||||
gen_server:cast(?MODULE, {enroll, self()}).
|
||||
|
||||
|
||||
-spec echo(Message) -> ok
|
||||
when Message :: string().
|
||||
%% @doc
|
||||
%% The function that tells the manager to broadcast a message to all clients.
|
||||
%% This can broadcast arbitrary strings to clients from non-clients as well.
|
||||
|
||||
echo(Message) ->
|
||||
gen_server:cast(?MODULE, {echo, Message, self()}).
|
||||
|
||||
|
||||
|
||||
%%% Startup Functions
|
||||
|
||||
|
||||
-spec start_link() -> Result
|
||||
when Result :: {ok, pid()}
|
||||
| {error, Reason :: term()}.
|
||||
%% @private
|
||||
%% This should only ever be called by 〘*PREFIX*〙clients (the service-level supervisor).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, none, []).
|
||||
|
||||
|
||||
-spec init(none) -> {ok, state()}.
|
||||
%% @private
|
||||
%% Called by the supervisor process to give the process a chance to perform any
|
||||
%% preparatory work necessary for proper function.
|
||||
|
||||
init(none) ->
|
||||
ok = io:format("Starting.~n"),
|
||||
State = #s{},
|
||||
{ok, State}.
|
||||
|
||||
|
||||
|
||||
%%% gen_server Message Handling Callbacks
|
||||
|
||||
|
||||
-spec handle_call(Message, From, State) -> Result
|
||||
when Message :: term(),
|
||||
From :: {pid(), reference()},
|
||||
State :: state(),
|
||||
Result :: {reply, Response, NewState}
|
||||
| {noreply, State},
|
||||
Response :: ok
|
||||
| {error, {listening, inet:port_number()}},
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% The gen_server:handle_call/3 callback.
|
||||
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3
|
||||
|
||||
handle_call({listen, PortNum}, _, State) ->
|
||||
{Response, NewState} = do_listen(PortNum, State),
|
||||
{reply, Response, NewState};
|
||||
handle_call(Unexpected, From, State) ->
|
||||
ok = io:format("~p Unexpected call from ~tp: ~tp~n", [self(), From, Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
-spec handle_cast(Message, State) -> {noreply, NewState}
|
||||
when Message :: term(),
|
||||
State :: state(),
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% The gen_server:handle_cast/2 callback.
|
||||
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2
|
||||
|
||||
handle_cast({enroll, Pid}, State) ->
|
||||
NewState = do_enroll(Pid, State),
|
||||
{noreply, NewState};
|
||||
handle_cast({echo, Message, Sender}, State) ->
|
||||
ok = do_echo(Message, Sender, State),
|
||||
{noreply, State};
|
||||
handle_cast(ignore, State) ->
|
||||
NewState = do_ignore(State),
|
||||
{noreply, NewState};
|
||||
handle_cast(Unexpected, State) ->
|
||||
ok = io:format("~p Unexpected cast: ~tp~n", [self(), Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
-spec handle_info(Message, State) -> {noreply, NewState}
|
||||
when Message :: term(),
|
||||
State :: state(),
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% The gen_server:handle_info/2 callback.
|
||||
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2
|
||||
|
||||
handle_info({'DOWN', Mon, process, Pid, Reason}, State) ->
|
||||
NewState = handle_down(Mon, Pid, Reason, State),
|
||||
{noreply, NewState};
|
||||
handle_info(Unexpected, State) ->
|
||||
ok = io:format("~p Unexpected info: ~tp~n", [self(), Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
|
||||
%%% OTP Service Functions
|
||||
|
||||
-spec code_change(OldVersion, State, Extra) -> Result
|
||||
when OldVersion :: {down, Version} | Version,
|
||||
Version :: term(),
|
||||
State :: state(),
|
||||
Extra :: term(),
|
||||
Result :: {ok, NewState}
|
||||
| {error, Reason :: term()},
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% The gen_server:code_change/3 callback.
|
||||
%% See: http://erlang.org/doc/man/gen_server.html#Module:code_change-3
|
||||
|
||||
code_change(_, State, _) ->
|
||||
{ok, State}.
|
||||
|
||||
|
||||
-spec terminate(Reason, State) -> no_return()
|
||||
when Reason :: normal
|
||||
| shutdown
|
||||
| {shutdown, term()}
|
||||
| term(),
|
||||
State :: state().
|
||||
%% @private
|
||||
%% The gen_server:terminate/2 callback.
|
||||
%% See: http://erlang.org/doc/man/gen_server.html#Module:terminate-2
|
||||
|
||||
terminate(_, _) ->
|
||||
ok.
|
||||
|
||||
|
||||
|
||||
%%% Doer Functions
|
||||
|
||||
-spec do_listen(PortNum, State) -> {Result, NewState}
|
||||
when PortNum :: inet:port_number(),
|
||||
State :: state(),
|
||||
Result :: ok
|
||||
| {error, Reason :: {listening, inet:port_number()}},
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% The "doer" procedure called when a "listen" message is received.
|
||||
|
||||
do_listen(PortNum, State = #s{port_num = none}) ->
|
||||
SocketOptions =
|
||||
[inet6,
|
||||
{active, once},
|
||||
{mode, binary},
|
||||
{keepalive, true},
|
||||
{reuseaddr, true}],
|
||||
{ok, Listener} = gen_tcp:listen(PortNum, SocketOptions),
|
||||
{ok, _} = 〘*PREFIX*〙client:start(Listener),
|
||||
{ok, State#s{port_num = PortNum, listener = Listener}};
|
||||
do_listen(_, State = #s{port_num = PortNum}) ->
|
||||
ok = io:format("~p Already listening on ~p~n", [self(), PortNum]),
|
||||
{{error, {listening, PortNum}}, State}.
|
||||
|
||||
|
||||
-spec do_ignore(State) -> NewState
|
||||
when State :: state(),
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% The "doer" procedure called when an "ignore" message is received.
|
||||
|
||||
do_ignore(State = #s{listener = none}) ->
|
||||
State;
|
||||
do_ignore(State = #s{listener = Listener}) ->
|
||||
ok = gen_tcp:close(Listener),
|
||||
State#s{listener = none}.
|
||||
|
||||
|
||||
-spec do_enroll(Pid, State) -> NewState
|
||||
when Pid :: pid(),
|
||||
State :: state(),
|
||||
NewState :: state().
|
||||
|
||||
do_enroll(Pid, State = #s{clients = Clients}) ->
|
||||
case lists:member(Pid, Clients) of
|
||||
false ->
|
||||
Mon = monitor(process, Pid),
|
||||
ok = io:format("Monitoring ~tp @ ~tp~n", [Pid, Mon]),
|
||||
State#s{clients = [Pid | Clients]};
|
||||
true ->
|
||||
State
|
||||
end.
|
||||
|
||||
|
||||
-spec do_echo(Message, Sender, State) -> ok
|
||||
when Message :: string(),
|
||||
Sender :: pid(),
|
||||
State :: state().
|
||||
%% @private
|
||||
%% The "doer" procedure called when an "echo" message is received.
|
||||
|
||||
do_echo(Message, Sender, #s{clients = Clients}) ->
|
||||
Send = fun(Client) -> Client ! {relay, Sender, Message} end,
|
||||
lists:foreach(Send, Clients).
|
||||
|
||||
|
||||
-spec handle_down(Mon, Pid, Reason, State) -> NewState
|
||||
when Mon :: reference(),
|
||||
Pid :: pid(),
|
||||
Reason :: term(),
|
||||
State :: state(),
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% Deal with monitors. When a new process enrolls as a client a monitor is set and
|
||||
%% the process is added to the client list. When the process terminates we receive
|
||||
%% a 'DOWN' message from the monitor. More sophisticated work managers typically have
|
||||
%% an "unenroll" function, but this echo service doesn't need one.
|
||||
|
||||
handle_down(Mon, Pid, Reason, State = #s{clients = Clients}) ->
|
||||
case lists:member(Pid, Clients) of
|
||||
true ->
|
||||
NewClients = lists:delete(Pid, Clients),
|
||||
State#s{clients = NewClients};
|
||||
false ->
|
||||
Unexpected = {'DOWN', Mon, process, Pid, Reason},
|
||||
ok = io:format("~p Unexpected info: ~tp~n", [self(), Unexpected]),
|
||||
State
|
||||
end.
|
||||
@ -0,0 +1,68 @@
|
||||
%%% @doc
|
||||
%%% 〘*PROJECT NAME*〙 Client Supervisor
|
||||
%%%
|
||||
%%% This process supervises the client socket handlers themselves. It is a peer of the
|
||||
%%% 〘*PREFIX*〙client_man (the manager interface to this network service component), and a
|
||||
%%% child of the supervisor named 〘*PREFIX*〙clients.
|
||||
%%%
|
||||
%%% Because we don't know (or care) how many client connections the server may end up
|
||||
%%% handling this is a simple_one_for_one supervisor which can spawn and manage as
|
||||
%%% many identically defined workers as required, but cannot supervise any other types
|
||||
%%% of processes (one of the tradeoffs of the "simple" in `simple_one_for_one').
|
||||
%%%
|
||||
%%% http://erlang.org/doc/design_principles/sup_princ.html#id79244
|
||||
%%% @end
|
||||
|
||||
-module(〘*PREFIX*〙client_sup).
|
||||
-behaviour(supervisor).
|
||||
〘*AUTHOR*〙
|
||||
〘*COPYRIGHT*〙
|
||||
〘*LICENSE*〙
|
||||
|
||||
-export([start_acceptor/1]).
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
|
||||
|
||||
-spec start_acceptor(ListenSocket) -> Result
|
||||
when ListenSocket :: gen_tcp:socket(),
|
||||
Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: {already_started, pid()}
|
||||
| {shutdown, term()}
|
||||
| term().
|
||||
%% @private
|
||||
%% Spawns the first listener at the request of the 〘*PREFIX*〙client_man when es:listen/1
|
||||
%% is called, or the next listener at the request of the currently listening 〘*PREFIX*〙client
|
||||
%% when a connection is made.
|
||||
%%
|
||||
%% Error conditions, supervision strategies and other important issues are
|
||||
%% explained in the supervisor module docs:
|
||||
%% http://erlang.org/doc/man/supervisor.html
|
||||
|
||||
start_acceptor(ListenSocket) ->
|
||||
supervisor:start_child(?MODULE, [ListenSocket]).
|
||||
|
||||
|
||||
-spec start_link() -> {ok, pid()}.
|
||||
%% @private
|
||||
%% This supervisor's own start function.
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, none).
|
||||
|
||||
|
||||
-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
%% @private
|
||||
%% The OTP init/1 function.
|
||||
|
||||
init(none) ->
|
||||
RestartStrategy = {simple_one_for_one, 1, 60},
|
||||
Client = {〘*PREFIX*〙client,
|
||||
{〘*PREFIX*〙client, start_link, []},
|
||||
temporary,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[〘*PREFIX*〙client]},
|
||||
{ok, {RestartStrategy, [Client]}}.
|
||||
47
zomp/lib/otpr/zx/0.1.0/templates/example_server/clients.erl
Normal file
47
zomp/lib/otpr/zx/0.1.0/templates/example_server/clients.erl
Normal file
@ -0,0 +1,47 @@
|
||||
%%% @doc
|
||||
%%% 〘*PROJECT NAME*〙 Client Service Supervisor
|
||||
%%%
|
||||
%%% This is the service-level supervisor of the system. It is the parent of both the
|
||||
%%% client connection handlers and the client manager (which manages the client
|
||||
%%% connection handlers). This is the child of 〘*PREFIX*〙sup.
|
||||
%%%
|
||||
%%% See: http://erlang.org/doc/apps/kernel/application.html
|
||||
%%% @end
|
||||
|
||||
-module(〘*PREFIX*〙clients).
|
||||
-behavior(supervisor).
|
||||
〘*AUTHOR*〙
|
||||
〘*COPYRIGHT*〙
|
||||
〘*LICENSE*〙
|
||||
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
|
||||
-spec start_link() -> {ok, pid()}.
|
||||
%% @private
|
||||
%% This supervisor's own start function.
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, none).
|
||||
|
||||
-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
%% @private
|
||||
%% The OTP init/1 function.
|
||||
|
||||
init(none) ->
|
||||
RestartStrategy = {rest_for_one, 1, 60},
|
||||
ClientSup = {〘*PREFIX*〙client_sup,
|
||||
{〘*PREFIX*〙client_sup, start_link, []},
|
||||
permanent,
|
||||
5000,
|
||||
supervisor,
|
||||
[〘*PREFIX*〙client_sup]},
|
||||
ClientMan = {〘*PREFIX*〙client_man,
|
||||
{〘*PREFIX*〙client_man, start_link, []},
|
||||
permanent,
|
||||
5000,
|
||||
worker,
|
||||
[〘*PREFIX*〙client_man]},
|
||||
Children = [ClientSup, ClientMan],
|
||||
{ok, {RestartStrategy, Children}}.
|
||||
43
zomp/lib/otpr/zx/0.1.0/templates/example_server/sup.erl
Normal file
43
zomp/lib/otpr/zx/0.1.0/templates/example_server/sup.erl
Normal file
@ -0,0 +1,43 @@
|
||||
%%% @doc
|
||||
%%% 〘*PROJECT NAME*〙 Top-level Supervisor
|
||||
%%%
|
||||
%%% The very top level supervisor in the system. It only has one service branch: the
|
||||
%%% client handling service. In a more complex system the client handling service would
|
||||
%%% only be one part of a larger system. Were this a game system, for example, the
|
||||
%%% item data management service would be a peer, as would a login credential provision
|
||||
%%% service, game world event handling, and so on.
|
||||
%%%
|
||||
%%% See: http://erlang.org/doc/design_principles/applications.html
|
||||
%%% See: http://zxq9.com/archives/1311
|
||||
%%% @end
|
||||
|
||||
-module(〘*PREFIX*〙sup).
|
||||
-behaviour(supervisor).
|
||||
〘*LICENSE*〙
|
||||
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
|
||||
-spec start_link() -> {ok, pid()}.
|
||||
%% @private
|
||||
%% This supervisor's own start function.
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
|
||||
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
%% @private
|
||||
%% The OTP init/1 function.
|
||||
|
||||
init([]) ->
|
||||
RestartStrategy = {one_for_one, 1, 60},
|
||||
Clients = {〘*PREFIX*〙clients,
|
||||
{〘*PREFIX*〙clients, start_link, []},
|
||||
permanent,
|
||||
5000,
|
||||
supervisor,
|
||||
[〘*PREFIX*〙clients]},
|
||||
Children = [Clients],
|
||||
{ok, {RestartStrategy, Children}}.
|
||||
4
zomp/lib/otpr/zx/0.1.0/zomp.meta
Normal file
4
zomp/lib/otpr/zx/0.1.0/zomp.meta
Normal file
@ -0,0 +1,4 @@
|
||||
{deps,[]}.
|
||||
{package_id,{"otpr","zx",{0,1,0}}}.
|
||||
{prefix,"zx_"}.
|
||||
{type,app}.
|
||||
Loading…
x
Reference in New Issue
Block a user