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
|
#!/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
|
pushd $(dirname $BASH_SOURCE) > /dev/null
|
||||||
ZX_DEV_ROOT=$PWD
|
ZX_DEV_ROOT=$PWD
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
@ -7,4 +11,4 @@ ZOMP_DIR="$ZX_DEV_ROOT/tester"
|
|||||||
rm -rf "$ZOMP_DIR"
|
rm -rf "$ZOMP_DIR"
|
||||||
cp -r "$ZX_DEV_ROOT/zomp" "$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"}.
|
{realm, "otpr"}.
|
||||||
{prime, {"otpr.psychobitch.party",11311}}.
|
{prime, {"zomp.psychobitch.party",11311}}.
|
||||||
{sysop, "zxq9"}.
|
{sysop, "zxq9"}.
|
||||||
{key, "zxq9-root"}.
|
{key, "zxq9-root"}.
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
{application, zx,
|
{application,zx,
|
||||||
[{description, "Zomp client program"},
|
[{description,"Zomp client program"},
|
||||||
{vsn, "0.1.0"},
|
{vsn,"0.1.0"},
|
||||||
{applications, [stdlib, kernel]},
|
{applications,[stdlib,kernel]},
|
||||||
{modules, [zx,
|
{modules,[zx,zx_auth,zx_conn,zx_conn_sup,zx_daemon,zx_key,
|
||||||
zx_sup,
|
zx_lib,zx_local,zx_net,zx_sup,zx_sys_conf,zx_tty]},
|
||||||
zx_daemon,
|
{mod,{zx,none}}]}.
|
||||||
zx_conn_sup,
|
|
||||||
zx_conn,
|
|
||||||
zx_lib,
|
|
||||||
zx_net]},
|
|
||||||
{mod, {zx, none}}]}.
|
|
||||||
|
|||||||
@ -31,6 +31,6 @@ log(Level, Format, Args) ->
|
|||||||
warning -> "[WARNING]";
|
warning -> "[WARNING]";
|
||||||
error -> "[ERROR]"
|
error -> "[ERROR]"
|
||||||
end,
|
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),
|
UTF8 = unicode:characters_to_binary(Out),
|
||||||
io:format(UTF8).
|
io:format(UTF8).
|
||||||
|
|||||||
@ -4,9 +4,14 @@
|
|||||||
|
|
||||||
main(Args) ->
|
main(Args) ->
|
||||||
true = code:add_patha("ebin"),
|
true = code:add_patha("ebin"),
|
||||||
up_to_date = make:all(),
|
case make:all() of
|
||||||
ok = lists:foreach(fun dispatch/1, Args),
|
up_to_date ->
|
||||||
halt(0).
|
lists:foreach(fun dispatch/1, Args),
|
||||||
|
halt(0);
|
||||||
|
error ->
|
||||||
|
ok = io:format("Build error. Halting.~n"),
|
||||||
|
halt(1)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
dispatch("edoc") ->
|
dispatch("edoc") ->
|
||||||
|
|||||||
@ -1,15 +1,26 @@
|
|||||||
%%% @doc
|
%%% @doc
|
||||||
%%% ZX
|
%%% ZX: A suite of tools for Erlang development and deployment.
|
||||||
%%%
|
%%%
|
||||||
%%% A general dependency and packaging tool that works together with the zomp
|
%%% ZX can:
|
||||||
%%% package manager. Given a project directory with a standard layout, zx can:
|
%%% - Create project templates for applications, libraries and escripts.
|
||||||
%%% - Initialize your project for packaging and semver tracking under zomp.
|
%%% - Initialize existing projects for packaging and management.
|
||||||
%%% - Add dependencies (recursively) defined in any zomp repository realm.
|
%%% - Create and manage zomp realms, users, keys, etc.
|
||||||
%%% - Update dependencies (recursively) defined in any zomp repository realm.
|
%%% - Manage dependencies hosted in any zomp realm.
|
||||||
%%% - Remove dependencies.
|
%%% - Package, submit, pull-for-review, and resign-to-accept packages.
|
||||||
%%% - Update, upgrade or run any application from source that zomp tracks.
|
%%% - Update, upgrade, and run any application from source that zomp tracks.
|
||||||
%%% - Locally install packages from files and locally stored public keys.
|
%%% - Locally install packages from files and locally stored public keys.
|
||||||
%%% - Build and run a local project from source using zomp dependencies.
|
%%% - 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
|
%%% @end
|
||||||
|
|
||||||
-module(zx).
|
-module(zx).
|
||||||
@ -30,7 +41,7 @@
|
|||||||
key_id/0, key_name/0,
|
key_id/0, key_name/0,
|
||||||
user_id/0, user_name/0, contact_info/0, user_data/0,
|
user_id/0, user_name/0, contact_info/0, user_data/0,
|
||||||
lower0_9/0, label/0,
|
lower0_9/0, label/0,
|
||||||
package_meta/0,
|
package_meta/0, ss_tag/0,
|
||||||
outcome/0]).
|
outcome/0]).
|
||||||
|
|
||||||
-include("zx_logger.hrl").
|
-include("zx_logger.hrl").
|
||||||
@ -48,20 +59,24 @@
|
|||||||
Minor :: non_neg_integer() | z,
|
Minor :: non_neg_integer() | z,
|
||||||
Patch :: non_neg_integer() | z}.
|
Patch :: non_neg_integer() | z}.
|
||||||
-type host() :: {string() | inet:ip_address(), inet:port_number()}.
|
-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_id() :: {realm(), key_name()}.
|
||||||
-type key_name() :: lower0_9().
|
-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(),
|
-type user_data() :: {ID :: user_id(),
|
||||||
RealName :: string(),
|
RealName :: string(),
|
||||||
Contact :: [contact_info()],
|
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 lower0_9() :: [$a..$z | $0..$9 | $_].
|
||||||
-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
||||||
-type package_meta() :: #{package_id := package_id(),
|
-type package_meta() :: #{package_id := package_id(),
|
||||||
deps := [package_id()],
|
deps := [package_id()],
|
||||||
type := app | lib}.
|
type := app | lib}.
|
||||||
|
-type ss_tag() :: {serial(), calendar:timestamp()}.
|
||||||
|
|
||||||
-type outcome() :: ok
|
-type outcome() :: ok
|
||||||
| {error, Reason :: term()}
|
| {error, Reason :: term()}
|
||||||
@ -75,6 +90,7 @@
|
|||||||
-spec do() -> no_return().
|
-spec do() -> no_return().
|
||||||
|
|
||||||
do() ->
|
do() ->
|
||||||
|
ok = io:setopts([{encoding, unicode}]),
|
||||||
do([]).
|
do([]).
|
||||||
|
|
||||||
|
|
||||||
@ -83,44 +99,16 @@ do() ->
|
|||||||
%% Dispatch work functions based on the nature of the input arguments.
|
%% Dispatch work functions based on the nature of the input arguments.
|
||||||
|
|
||||||
do(["help"]) ->
|
do(["help"]) ->
|
||||||
usage_exit(0);
|
done(help(top));
|
||||||
do(["run", PackageString | Args]) ->
|
do(["help", "user"]) ->
|
||||||
|
done(help(user));
|
||||||
|
do(["help", "dev"]) ->
|
||||||
|
done(help(dev));
|
||||||
|
do(["help", "sysop"]) ->
|
||||||
|
done(help(sysop));
|
||||||
|
do(["run", PackageString | ArgV]) ->
|
||||||
ok = start(),
|
ok = start(),
|
||||||
run(PackageString, Args);
|
not_done(run(PackageString, ArgV));
|
||||||
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());
|
|
||||||
do(["list", "realms"]) ->
|
do(["list", "realms"]) ->
|
||||||
done(zx_local:list_realms());
|
done(zx_local:list_realms());
|
||||||
do(["list", "packages", Realm]) ->
|
do(["list", "packages", Realm]) ->
|
||||||
@ -134,67 +122,121 @@ do(["latest", PackageString]) ->
|
|||||||
done(zx_local:latest(PackageString));
|
done(zx_local:latest(PackageString));
|
||||||
do(["import", "realm", RealmFile]) ->
|
do(["import", "realm", RealmFile]) ->
|
||||||
done(zx_local: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]) ->
|
do(["drop", "dep", PackageString]) ->
|
||||||
PackageID = zx_lib:package_id(PackageString),
|
PackageID = zx_lib:package_id(PackageString),
|
||||||
done(zx_local:drop_dep(PackageID));
|
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"]) ->
|
do(["package"]) ->
|
||||||
{ok, TargetDir} = file:get_cwd(),
|
{ok, TargetDir} = file:get_cwd(),
|
||||||
zx_local:package(TargetDir);
|
done(zx_local:package(TargetDir));
|
||||||
do(["package", TargetDir]) ->
|
do(["package", TargetDir]) ->
|
||||||
case filelib:is_dir(TargetDir) of
|
case filelib:is_dir(TargetDir) of
|
||||||
true -> done(zx_local:package(TargetDir));
|
true -> done(zx_local:package(TargetDir));
|
||||||
false -> done({error, "Target directory does not exist", 22})
|
false -> done({error, "Target directory does not exist", 22})
|
||||||
end;
|
end;
|
||||||
do(["dialyze"]) ->
|
do(["submit", PackageFile]) ->
|
||||||
done(zx_local:dialyze());
|
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"]) ->
|
do(["create", "user"]) ->
|
||||||
done(zx_local:create_user());
|
done(zx_local:create_user());
|
||||||
do(["create", "userfile"]) ->
|
do(["create", "userfile"]) ->
|
||||||
done(zx_local:create_userfile());
|
done(zx_local:create_userfile());
|
||||||
|
do(["create", "keypair"]) ->
|
||||||
|
done(zx_local:grow_a_pair());
|
||||||
do(["export", "user"]) ->
|
do(["export", "user"]) ->
|
||||||
done(zx_local:export_user());
|
done(zx_local:export_user());
|
||||||
do(["import", "user", ZdufFile]) ->
|
do(["import", "user", ZdufFile]) ->
|
||||||
done(zx_local:import_user(ZdufFile));
|
done(zx_local:import_user(ZdufFile));
|
||||||
do(["create", "keypair"]) ->
|
do(["list", "packagers", PackageName]) ->
|
||||||
done(zx_local:grow_a_pair());
|
done(zx_auth:list_packagers(PackageName));
|
||||||
do(["drop", "key", Realm, KeyName]) ->
|
do(["list", "maintainers", PackageName]) ->
|
||||||
done(zx_local:drop_key({Realm, KeyName}));
|
done(zx_auth:list_maintainers(PackageName));
|
||||||
do(["create", "plt"]) ->
|
do(["list", "sysops", Realm]) ->
|
||||||
done(zx_local:create_plt());
|
done(zx_auth:list_sysops(Realm));
|
||||||
do(["create", "realm"]) ->
|
|
||||||
done(zx_local:create_realm());
|
|
||||||
do(["create", "realmfile"]) ->
|
do(["create", "realmfile"]) ->
|
||||||
done(zx_local: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]) ->
|
do(["takeover", Realm]) ->
|
||||||
done(zx_local:takeover(Realm));
|
done(zx_local:takeover(Realm));
|
||||||
do(["abdicate", Realm]) ->
|
do(["abdicate", Realm]) ->
|
||||||
done(zx_local: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(_) ->
|
do(_) ->
|
||||||
usage_exit(22).
|
usage_exit(22).
|
||||||
|
|
||||||
@ -203,16 +245,23 @@ do(_) ->
|
|||||||
|
|
||||||
done(ok) ->
|
done(ok) ->
|
||||||
halt(0);
|
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) ->
|
done({error, Code}) when is_integer(Code) ->
|
||||||
|
ok = log(error, "Operation failed with code: ~w", [Code]),
|
||||||
halt(Code);
|
halt(Code);
|
||||||
|
done({error, Reason}) ->
|
||||||
|
ok = log(error, "Operation failed with: ~160tp", [Reason]),
|
||||||
|
halt(1);
|
||||||
done({error, Info, Code}) ->
|
done({error, Info, Code}) ->
|
||||||
ok = log(error, Info),
|
ok = log(error, Info),
|
||||||
halt(Code).
|
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()
|
-spec compatibility_check(Platforms) -> ok | no_return()
|
||||||
when Platforms :: unix | win32.
|
when Platforms :: unix | win32.
|
||||||
%% @private
|
%% @private
|
||||||
@ -229,7 +278,7 @@ compatibility_check(Platforms) ->
|
|||||||
true ->
|
true ->
|
||||||
ok;
|
ok;
|
||||||
false ->
|
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]),
|
ok = log(error, Message, [Family, Name]),
|
||||||
halt(0)
|
halt(0)
|
||||||
end.
|
end.
|
||||||
@ -249,6 +298,7 @@ compatibility_check(Platforms) ->
|
|||||||
%% @equiv application:ensure_started(zx).
|
%% @equiv application:ensure_started(zx).
|
||||||
|
|
||||||
start() ->
|
start() ->
|
||||||
|
% ok = application:ensure_started(sasl),
|
||||||
ok = application:ensure_started(zx),
|
ok = application:ensure_started(zx),
|
||||||
zx_daemon:init_connections().
|
zx_daemon:init_connections().
|
||||||
|
|
||||||
@ -321,9 +371,9 @@ unsubscribe() ->
|
|||||||
|
|
||||||
%%% Execution of application
|
%%% Execution of application
|
||||||
|
|
||||||
-spec run(Identifier, RunArgs) -> no_return()
|
-spec run(PackageString, RunArgs) -> no_return()
|
||||||
when Identifier :: string(),
|
when PackageString :: string(),
|
||||||
RunArgs :: [string()].
|
RunArgs :: [string()].
|
||||||
%% @private
|
%% @private
|
||||||
%% Given a program Identifier and a list of Args, attempt to locate the program and its
|
%% 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
|
%% 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
|
%% If there is a problem anywhere in the locating, discovery, building, and loading
|
||||||
%% procedure the runtime will halt with an error message.
|
%% procedure the runtime will halt with an error message.
|
||||||
|
|
||||||
run(Identifier, RunArgs) ->
|
run(PackageString, RunArgs) ->
|
||||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
case zx_lib:package_id(PackageString) of
|
||||||
FuzzyID =
|
{ok, FuzzyID} -> run2(FuzzyID, RunArgs);
|
||||||
case zx_lib:package_id(Identifier) of
|
Error -> log(info, "run/2 got ~tp", [Error]), Error
|
||||||
{ok, Fuzzy} ->
|
end.
|
||||||
Fuzzy;
|
|
||||||
{error, invalid_package_string} ->
|
|
||||||
error_exit("Bad package string: ~ts", [Identifier], ?LINE)
|
run2(FuzzyID = {Realm, Name, _}, RunArgs) ->
|
||||||
end,
|
case resolve_installed_version(FuzzyID) of
|
||||||
{ok, PackageID} = ensure_installed(FuzzyID),
|
exact -> run3(FuzzyID, RunArgs);
|
||||||
ok = build(PackageID),
|
{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),
|
Dir = zx_lib:ppath(lib, PackageID),
|
||||||
{ok, Meta} = zx_lib:read_project_meta(Dir),
|
{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()
|
-spec run_local(RunArgs) -> no_return()
|
||||||
@ -370,52 +447,72 @@ run(Identifier, RunArgs) ->
|
|||||||
|
|
||||||
run_local(RunArgs) ->
|
run_local(RunArgs) ->
|
||||||
{ok, Meta} = zx_lib:read_project_meta(),
|
{ok, Meta} = zx_lib:read_project_meta(),
|
||||||
PackageID = maps:get(package_id, Meta),
|
PackageID = {_, Name, _} = maps:get(package_id, Meta),
|
||||||
ok = zx_lib:build(),
|
Type = maps:get(type, Meta),
|
||||||
|
Deps = maps:get(deps, Meta),
|
||||||
{ok, Dir} = file:get_cwd(),
|
{ok, Dir} = file:get_cwd(),
|
||||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
true = os:putenv(Name ++ "_include", filename:join(Dir, "include")),
|
||||||
prepare(PackageID, Meta, Dir, RunArgs).
|
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()
|
-spec prepare([zx:package_id()]) -> ok.
|
||||||
when PackageID :: package_id(),
|
|
||||||
Meta :: package_meta(),
|
|
||||||
Dir :: file:filename(),
|
|
||||||
RunArgs :: [string()].
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Execution prep common to all packages.
|
%% Execution prep common to all packages.
|
||||||
|
|
||||||
prepare(PackageID, Meta, Dir, RunArgs) ->
|
prepare(Deps) ->
|
||||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
true = os:putenv("zx_include", filename:join(os:getenv("ZX_DIR"), "include")),
|
||||||
ok = log(info, "Preparing ~ts...", [PackageString]),
|
ok = lists:foreach(fun include_env/1, Deps),
|
||||||
Type = maps:get(type, Meta),
|
|
||||||
Deps = maps:get(deps, Meta),
|
|
||||||
NotInstalled = fun(P) -> not filelib:is_dir(zx_lib:ppath(lib, P)) end,
|
NotInstalled = fun(P) -> not filelib:is_dir(zx_lib:ppath(lib, P)) end,
|
||||||
Needed = lists:filter(NotInstalled, Deps),
|
Needed = lists:filter(NotInstalled, Deps),
|
||||||
ok = lists:foreach(fun install/1, Needed),
|
acquire(Needed, Deps).
|
||||||
ok = lists:foreach(fun build/1, Needed),
|
|
||||||
execute(Type, PackageID, Meta, Dir, RunArgs).
|
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().
|
include_env(PackageID = {_, Name, _}) ->
|
||||||
%% @private
|
Path = filename:join(zx_lib:ppath(lib, PackageID), "include"),
|
||||||
%% Installs a package from upstream.
|
os:putenv(Name ++ "_include", Path).
|
||||||
|
|
||||||
install(PackageString) ->
|
|
||||||
{ok, ID} = zx_daemon:install(PackageString),
|
|
||||||
install(PackageString, ID).
|
|
||||||
|
|
||||||
|
|
||||||
install(PackageString, ID) ->
|
-spec fetch(zx:package_id()) -> zx:outcome().
|
||||||
|
|
||||||
|
fetch(PackageID) ->
|
||||||
|
{ok, ID} = zx_daemon:fetch(PackageID),
|
||||||
|
fetch2(ID).
|
||||||
|
|
||||||
|
fetch2(ID) ->
|
||||||
receive
|
receive
|
||||||
{result, ID, done} ->
|
{result, ID, done} ->
|
||||||
ok;
|
ok;
|
||||||
{result, ID, {hops, Count}} ->
|
{result, ID, {hops, Count}} ->
|
||||||
ok = log(info, "~ts ~w hops away.", [PackageString, Count]),
|
ok = log(info, "Inbound; ~w hops away.", [Count]),
|
||||||
install(PackageString, ID);
|
fetch2(ID);
|
||||||
{result, ID, {error, Reason}} ->
|
{result, ID, {error, Reason}} ->
|
||||||
{error, Reason, 1}
|
{error, Reason, 1}
|
||||||
after 60000 ->
|
after 10000 ->
|
||||||
{error, timeout, 62}
|
{error, timeout, 62}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -434,11 +531,9 @@ execute(app, PackageID, Meta, Dir, RunArgs) ->
|
|||||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||||
ok = log(info, "Starting ~ts.", [PackageString]),
|
ok = log(info, "Starting ~ts.", [PackageString]),
|
||||||
Name = element(2, PackageID),
|
Name = element(2, PackageID),
|
||||||
AppTag = list_to_atom(Name),
|
|
||||||
{AppMod, _} = maps:get(appmod, Meta),
|
|
||||||
ok = zx_daemon:pass_meta(Meta, Dir, RunArgs),
|
ok = zx_daemon:pass_meta(Meta, Dir, RunArgs),
|
||||||
|
AppTag = list_to_atom(Name),
|
||||||
ok = ensure_all_started(AppTag),
|
ok = ensure_all_started(AppTag),
|
||||||
ok = pass_argv(AppMod, RunArgs),
|
|
||||||
log(info, "Launcher complete.");
|
log(info, "Launcher complete.");
|
||||||
execute(lib, PackageID, _, _, _) ->
|
execute(lib, PackageID, _, _, _) ->
|
||||||
Message = "Lib ~ts is available on the system, but is not a standalone app.",
|
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) ->
|
ensure_all_started(AppMod) ->
|
||||||
case application:ensure_all_started(AppMod) of
|
case application:ensure_all_started(AppMod) of
|
||||||
{ok, []} -> ok;
|
{ok, []} -> ok;
|
||||||
{ok, Apps} -> log(info, "Started ~tp", [Apps])
|
{ok, Apps} -> log(info, "Started ~160tp", [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)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -546,26 +590,11 @@ tuplize(String, Acc) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec build(package_id()) -> ok.
|
wait_result(ID) ->
|
||||||
%% @private
|
receive
|
||||||
%% Given an AppID, build the project from source and add it to the current lib path.
|
{result, ID, Result} -> Result
|
||||||
|
after 5000 -> {error, timeout}
|
||||||
build(PackageID) ->
|
end.
|
||||||
{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).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -578,100 +607,105 @@ build(PackageID) ->
|
|||||||
%% with the provided exit code.
|
%% with the provided exit code.
|
||||||
|
|
||||||
usage_exit(Code) ->
|
usage_exit(Code) ->
|
||||||
ok = usage(),
|
ok = lists:foreach(fun io:format/1, usage_all()),
|
||||||
halt(Code).
|
halt(Code).
|
||||||
|
|
||||||
|
|
||||||
-spec usage() -> ok.
|
help(top) -> show_help();
|
||||||
%% @private
|
help(user) -> show_help([usage_header(), usage_user(), usage_spec()]);
|
||||||
%% Display the zx command line usage message.
|
help(dev) -> show_help([usage_header(), usage_dev(), usage_spec()]);
|
||||||
|
help(sysop) -> show_help([usage_header(), usage_sysop(), usage_spec()]).
|
||||||
|
|
||||||
usage() ->
|
|
||||||
|
show_help() ->
|
||||||
T =
|
T =
|
||||||
"ZX usage: zx [command] [object] [args]~n"
|
"ZX help has three forms, one for each category of commands:~n"
|
||||||
"~n"
|
" zx help [user | dev | sysop]~n"
|
||||||
"User Actions:~n"
|
"The user manual is also available online at: http://zxq9.com/projects/zomp/~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",
|
|
||||||
io:format(T).
|
io:format(T).
|
||||||
|
|
||||||
|
|
||||||
|
show_help(Info) -> lists:foreach(fun io:format/1, Info).
|
||||||
|
|
||||||
%%% Error exits
|
|
||||||
|
|
||||||
-spec error_exit(Format, Args, Line) -> no_return()
|
usage_all() ->
|
||||||
when Format :: string(),
|
[usage_header(), usage_user(), usage_dev(), usage_sysop(), usage_spec()].
|
||||||
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) ->
|
usage_header() ->
|
||||||
File = filename:basename(?FILE),
|
"ZX usage: zx [command] [object] [args]~n~n".
|
||||||
ok = log(error, "~ts:~tp: " ++ Format, [File, Line | Args]),
|
|
||||||
halt(1).
|
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>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
-license("GPL-3.0").
|
-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,
|
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]).
|
add_package/1]).
|
||||||
|
|
||||||
-include("zx_logger.hrl").
|
-include("zx_logger.hrl").
|
||||||
@ -25,453 +29,570 @@
|
|||||||
%%% Functions
|
%%% Functions
|
||||||
|
|
||||||
|
|
||||||
-spec list_pending(PackageName :: string()) -> no_return().
|
-spec list_pending(PackageName :: string()) -> zx:outcome().
|
||||||
%% @private
|
%% @private
|
||||||
%% List the versions of a package that are pending review. The package name is input by
|
%% 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
|
%% 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").
|
%% package IDs, printed one per line to stdout (like "otpr-zomp-3.2.2").
|
||||||
|
|
||||||
list_pending(PackageName) ->
|
list_pending(PackageName) ->
|
||||||
Package = {Realm, Name} =
|
case zx_lib:package_id(PackageName) of
|
||||||
case zx_lib:package_id(PackageName) of
|
{ok, {Realm, Name, {z, z, z}}} -> list_pending2(Realm, Name);
|
||||||
{ok, {R, N, {z, z, z}}} ->
|
Error -> Error
|
||||||
{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)
|
|
||||||
end.
|
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
|
%% @private
|
||||||
%% List the package ids of all packages waiting in the resign queue for the given realm,
|
%% List the package ids of all packages waiting in the resign queue for the given realm,
|
||||||
%% printed to stdout one per line.
|
%% printed to stdout one per line.
|
||||||
|
|
||||||
list_resigns(Realm) ->
|
list_approved(Realm) ->
|
||||||
case zx_daemon:list_resigns(Realm) of
|
case connect(Realm) of
|
||||||
{ok, []} ->
|
{ok, Socket} -> list_approved2(Realm, Socket);
|
||||||
Message = "No packages pending signature in ~tp.",
|
Error -> Error
|
||||||
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)
|
|
||||||
end.
|
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().
|
-spec submit(ZspPath :: file:filename()) -> zx:outcome().
|
||||||
%% @private
|
%% @private
|
||||||
%% Submit a package to the appropriate "prime" server for the given realm.
|
%% Submit a package to the appropriate "prime" server for the given realm.
|
||||||
|
|
||||||
submit(ZspPath) ->
|
submit(ZspPath) ->
|
||||||
{ok, ZspBin} = file:read_file(ZspPath),
|
case file:read_file(ZspPath) of
|
||||||
<<SigSize:24, Sig:SigSize/binary, Signed/binary>> = ZspBin,
|
{ok, ZspBin} -> submit2(ZspBin);
|
||||||
<<MetaSize:16, MetaBin:MetaSize/binary, _/binary>> = Signed,
|
Error -> Error
|
||||||
{ok, {PackageID, SigKeyName, _}} = zx_lib:b_to_ts(MetaBin),
|
end.
|
||||||
{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).
|
|
||||||
|
|
||||||
|
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) ->
|
review(PackageString) ->
|
||||||
PackageID = {Realm, _, _} = zx_lib:package_id(PackageString),
|
case zx_lib:package_id(PackageString) of
|
||||||
Socket = connect_auth_or_die(Realm),
|
{ok, PackageID} -> review2(PackageID);
|
||||||
ok = send(Socket, {review, PackageID}),
|
Error -> Error
|
||||||
{ok, ZrpBin} = recv_or_die(Socket),
|
end.
|
||||||
ok = send(Socket, ok),
|
|
||||||
ok = disconnect(Socket),
|
review2(PackageID = {Realm, _, _}) ->
|
||||||
{ok, Files} = erl_tar:extract({binary, ZrpBin}, [memory]),
|
case connect(Realm) of
|
||||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
{ok, Socket} -> review3(PackageID, Socket);
|
||||||
Meta = binary_to_term(MetaBin, [safe]),
|
Error -> Error
|
||||||
PackageID = maps:get(package_id, Meta),
|
end.
|
||||||
{KeyID, Signature} = maps:get(sig, Meta),
|
|
||||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
review3(PackageID, Socket) ->
|
||||||
TgzFile = PackageString ++ ".tgz",
|
Command = 8,
|
||||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
TermBin = term_to_binary(PackageID),
|
||||||
ok = zx_key:verify(TgzData, Signature, PubKey),
|
Request = <<0:24, Command:8, TermBin/binary>>,
|
||||||
ok =
|
ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>),
|
||||||
case file:make_dir(PackageString) of
|
receive
|
||||||
ok ->
|
{tcp, Socket, <<0:1, 0:7>>} -> review4(PackageID, Socket);
|
||||||
log(info, "Will unpack to directory ./~ts", [PackageString]);
|
{tcp, Socket, Bin} -> done(Socket, Bin);
|
||||||
{error, Error} ->
|
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||||
Message = "Creating dir ./~ts failed with ~ts. Aborting.",
|
after 5000 -> done(Socket, timeout)
|
||||||
ok = log(error, Message, [PackageString, Error]),
|
end.
|
||||||
halt(1)
|
|
||||||
end,
|
review4(PackageID, Socket) ->
|
||||||
ok = erl_tar:extract({binary, TgzData}, [compressed, {cwd, PackageString}]),
|
case zx_net:rx(Socket) of
|
||||||
ok = log(info, "Unpacked and awaiting inspection."),
|
{ok, <<Size:24, Sig:Size/binary, Signed/binary>>} ->
|
||||||
halt(0).
|
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, _, _}) ->
|
review7(PackageID, SigKey, Signed, Sig, TgzBin) ->
|
||||||
Socket = connect_auth_or_die(Realm),
|
case zx_key:verify(Signed, Sig, SigKey) of
|
||||||
ok = send(Socket, {approve, PackageID}),
|
true -> review8(PackageID, TgzBin);
|
||||||
ok = recv_or_die(Socket),
|
false -> {error, bad_sig}
|
||||||
ok = log(info, "ok"),
|
end.
|
||||||
halt(0).
|
|
||||||
|
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, _, _}) ->
|
approve(Package) -> package_operation(9, Package).
|
||||||
Socket = connect_auth_or_die(Realm),
|
|
||||||
ok = send(Socket, {reject, PackageID}),
|
|
||||||
ok = recv_or_die(Socket),
|
|
||||||
ok = log(info, "ok"),
|
|
||||||
halt(0).
|
|
||||||
|
|
||||||
|
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) ->
|
accept(PackageString) ->
|
||||||
PackageID = {Realm, _, _} = zx_lib:package_id(PackageString),
|
case zx_lib:package_id(PackageString) of
|
||||||
RealmConf = zx_lib:load_realm_conf(Realm),
|
{ok, PackageID} -> accept2(PackageID);
|
||||||
{package_keys, PackageKeys} = lists:keyfind(package_keys, 1, RealmConf),
|
Error -> Error
|
||||||
KeySelection = [{K, {R, K}} || {R, K} <- [element(1, K) || K <- PackageKeys]],
|
end.
|
||||||
PackageKeyID = zx_tty:select(KeySelection),
|
|
||||||
{ok, PackageKey} = zx_key:load(private, PackageKeyID),
|
accept2(PackageID = {Realm, Name, Version}) ->
|
||||||
Socket = connect_auth_or_die(Realm),
|
case connect_auth(Realm) of
|
||||||
ok = send(Socket, {accept, PackageID}),
|
{ok, AuthConn} -> accept3(PackageID, {Name, Version}, AuthConn);
|
||||||
{ok, ZrpBin} = recv_or_die(Socket),
|
Error -> Error
|
||||||
{ok, Files} = erl_tar:extract({binary, ZrpBin}, [memory]),
|
end.
|
||||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
|
||||||
Meta = binary_to_term(MetaBin, [safe]),
|
accept3(PackageID, PV, {UserName, KeyName, Key, Tag, Socket}) ->
|
||||||
PackageID = maps:get(package_id, Meta),
|
Command = 11,
|
||||||
{KeyID, Signature} = maps:get(sig, Meta),
|
Payload = {Tag, UserName, KeyName, PV},
|
||||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
Request = pack_and_sign(Command, Payload, Key),
|
||||||
TgzFile = PackageString ++ ".tgz",
|
ok = gen_tcp:send(Socket, <<0:8, Request/binary>>),
|
||||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
ok = inet:setopts(Socket, [{active, once}]),
|
||||||
ok = zx_key:verify(TgzData, Signature, PubKey),
|
receive
|
||||||
ReSignature = public_key:sign(TgzData, sha512, PackageKey),
|
{tcp, Socket, <<0:1, 0:7>>} -> accept4(PackageID, KeyName, Key, Socket);
|
||||||
FinalMeta = maps:put(sig, {PackageKeyID, ReSignature}, Meta),
|
{tcp, Socket, Bin} -> done(Socket, Bin);
|
||||||
NewMetaBin = term_to_binary(FinalMeta),
|
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||||
NewFiles = lists:keystore("zomp.meta", 1, Files, {"zomp.meta", NewMetaBin}),
|
after 5000 -> done(Socket, timeout)
|
||||||
ResignedZrp = PackageString ++ ".zsp.resign",
|
end.
|
||||||
ok = erl_tar:create(ResignedZrp, NewFiles),
|
|
||||||
{ok, ResignedBin} = file:read_file(ResignedZrp),
|
accept4(PackageID, KeyName, Key, Socket) ->
|
||||||
ok = gen_tcp:send(Socket, ResignedBin),
|
case zx_net:rx(Socket) of
|
||||||
ok = recv_or_die(Socket),
|
{ok, <<Size:24, Sig:Size/binary, Signed/binary>>} ->
|
||||||
ok = file:delete(ResignedZrp),
|
accept5(PackageID, {KeyName, Key, Socket}, Sig, Signed);
|
||||||
ok = recv_or_die(Socket),
|
Error ->
|
||||||
ok = disconnect(Socket),
|
done(Socket, Error)
|
||||||
ok = log(info, "Resigned ~ts", [PackageString]),
|
end.
|
||||||
halt(0).
|
|
||||||
|
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) ->
|
list_users(Realm) ->
|
||||||
ok = log(info, "Would add ~ts to packagers for ~160tp now.", [UserFile, Package]),
|
list_users(2, Realm).
|
||||||
halt(0).
|
|
||||||
|
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) ->
|
-spec list_users(Command, Target) -> zx:outcome()
|
||||||
ok = log(info, "Would add ~ts to maintainer for ~160tp now.", [UserFile, Package]),
|
when Command :: 2..5,
|
||||||
halt(0).
|
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) ->
|
add_sysop(UserFile) ->
|
||||||
ok = log(info, "Would add ~ts to sysop list.", [UserFile]),
|
ok = log(info, "Would add ~ts to sysop list.", [UserFile]),
|
||||||
halt(0).
|
{error, "Not yet implemented", 1}.
|
||||||
|
|
||||||
|
|
||||||
-spec add_package(PackageName) -> no_return()
|
-spec add_package(zx:package()) -> zx:outcome().
|
||||||
when PackageName :: zx:package().
|
|
||||||
%% @private
|
|
||||||
%% A sysop command that adds a package to a realm operated by the caller.
|
|
||||||
|
|
||||||
add_package(PackageName) ->
|
add_package(PackageName) ->
|
||||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||||
case zx_lib:package_id(PackageName) of
|
case zx_lib:package_id(PackageName) of
|
||||||
{ok, {Realm, Name, {z, z, z}}} ->
|
{ok, {Realm, Name, {z, z, z}}} -> add_package2(Realm, Name);
|
||||||
add_package(Realm, Name);
|
Error -> Error
|
||||||
_ ->
|
|
||||||
error_exit("~tp is not a valid package name.", [PackageName], ?LINE)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
add_package2(Realm, Name) ->
|
||||||
-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) ->
|
|
||||||
case connect_auth(Realm) of
|
case connect_auth(Realm) of
|
||||||
{ok, Socket} ->
|
{ok, AuthConn} -> add_package3(Realm, Name, AuthConn);
|
||||||
Socket;
|
Error -> Error
|
||||||
Error ->
|
|
||||||
M1 = "Connection failed to realm prime with ~160tp.",
|
|
||||||
ok = log(warning, M1, [Error]),
|
|
||||||
halt(1)
|
|
||||||
end.
|
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
|
-spec connect_auth(Realm) -> Result
|
||||||
when Realm :: zx:realm(),
|
when Realm :: zx:realm(),
|
||||||
Result :: {ok, gen_tcp:socket()}
|
Result :: {ok, AuthConn}
|
||||||
| {error, Reason :: term()}.
|
| {error, Reason},
|
||||||
|
AuthConn :: {UserName :: zx:user_name(),
|
||||||
|
KeyName :: zx:key_name(),
|
||||||
|
Key :: term(),
|
||||||
|
SSTag :: zx:ss_tag(),
|
||||||
|
Socket :: gen_tcp:socket()},
|
||||||
|
Reason :: term().
|
||||||
%% @private
|
%% @private
|
||||||
%% Connect to one of the servers in the realm constellation.
|
%% Connect to one of the servers in the realm constellation.
|
||||||
|
|
||||||
connect_auth(Realm) ->
|
connect_auth(Realm) ->
|
||||||
RealmConf = zx_lib:load_realm_conf(Realm),
|
UserData = prep_auth(Realm),
|
||||||
{User, KeyID, Key} = prep_auth(Realm, RealmConf),
|
case zx_lib:load_realm_conf(Realm) of
|
||||||
{prime, {Host, Port}} = lists:keyfind(prime, 1, RealmConf),
|
{ok, RealmConf} ->
|
||||||
Options = [{packet, 4}, {mode, binary}, {active, true}],
|
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
|
case gen_tcp:connect(Host, Port, Options, 5000) of
|
||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
ok = log(info, "Connected to ~tp prime.", [Realm]),
|
connect_auth3(Socket, Realm, UserData);
|
||||||
connect_auth(Socket, Realm, User, KeyID, Key);
|
|
||||||
Error = {error, E} ->
|
Error = {error, E} ->
|
||||||
ok = log(warning, "Connection problem: ~tp", [E]),
|
ok = log(warning, "Connection problem: ~160tp", [E]),
|
||||||
{error, Error}
|
{error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
connect_auth3(Socket, Realm, UD = {UserName, KeyName, Key}) ->
|
||||||
-spec connect_auth(Socket, Realm, User, KeyID, Key) -> Result
|
Null = 0,
|
||||||
when Socket :: gen_tcp:socket(),
|
Timestamp = calendar:universal_time(),
|
||||||
Realm :: zx:realm(),
|
Payload = {Realm, Timestamp, UserName, KeyName},
|
||||||
User :: zx:user_id(),
|
NullRequest = pack_and_sign(Null, Payload, Key),
|
||||||
KeyID :: zx:key_id(),
|
ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", NullRequest/binary>>),
|
||||||
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">>),
|
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, Bin} ->
|
{tcp, Socket, <<0:8, Bin/binary>>} -> connect_auth4(Socket, UD, Bin);
|
||||||
ok = binary_to_term(Bin, [safe]),
|
{tcp, Socket, Bin} -> done(Socket, Bin);
|
||||||
confirm_auth(Socket, Realm, User, KeyID, Key);
|
{tcp_closed, Socket} -> {error, tcp_closed}
|
||||||
{tcp_closed, Socket} ->
|
after 5000 -> done(Socket, timeout)
|
||||||
ok = log(warning, "Socket closed unexpectedly."),
|
end.
|
||||||
halt(1)
|
|
||||||
after 5000 ->
|
connect_auth4(Socket, {UserName, KeyName, Key}, Bin) ->
|
||||||
ok = log(warning, "Host realm ~160tp prime timed out.", [Realm]),
|
case zx_lib:b_to_ts(Bin) of
|
||||||
{error, auth_timeout}
|
{ok, Tag} -> {ok, {UserName, KeyName, Key, Tag, Socket}};
|
||||||
|
error -> done(Socket, bad_response)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
confirm_auth(Socket, Realm, User, KeyID, Key) ->
|
connect(Realm) ->
|
||||||
ok = send(Socket, {User, KeyID}),
|
case zx_lib:load_realm_conf(Realm) of
|
||||||
receive
|
{ok, RealmConf} ->
|
||||||
{tcp, Socket, Bin} ->
|
{Host, Port} = maps:get(prime, RealmConf),
|
||||||
case binary_to_term(Bin, [safe]) of
|
Options = [{packet, 4}, {mode, binary}, {nodelay, true}, {active, once}],
|
||||||
{sign, Blob} ->
|
gen_tcp:connect(Host, Port, Options, 5000);
|
||||||
Sig = public_key:sign(Blob, sha512, Key),
|
Error ->
|
||||||
ok = send(Socket, {signed, Sig}),
|
ok = log(error, "Realm ~160tp is not configured.", [Realm]),
|
||||||
confirm_auth(Socket);
|
Error
|
||||||
{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}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
confirm_auth(Socket) ->
|
-spec prep_auth(Realm) -> {User, KeyName, Key}
|
||||||
receive
|
when Realm :: zx:realm(),
|
||||||
{tcp, Socket, Bin} ->
|
User :: zx:user_id(),
|
||||||
case binary_to_term(Bin, [safe]) of
|
KeyName :: zx:key_id(),
|
||||||
ok -> {ok, Socket};
|
Key :: term().
|
||||||
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().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Loads the appropriate User, KeyID and reads in a registered key for use in
|
%% Loads the appropriate User, KeyID and reads in a registered key for use in
|
||||||
%% connect_auth/4.
|
%% connect_auth/4.
|
||||||
|
|
||||||
prep_auth(Realm, RealmConf) ->
|
prep_auth(Realm) ->
|
||||||
UsersFile = filename:join(zx_lib:zomp_dir(), "zomp.users"),
|
UserName = zx_local:select_user(Realm),
|
||||||
Users =
|
KeyName = zx_local:select_private_key({Realm, UserName}),
|
||||||
case file:consult(UsersFile) of
|
{ok, Key} = zx_key:load(private, {Realm, KeyName}),
|
||||||
{ok, U} ->
|
{UserName, KeyName, Key}.
|
||||||
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}.
|
|
||||||
|
|
||||||
|
|
||||||
-spec disconnect(gen_tcp:socket()) -> ok.
|
pack_and_sign(Command, Payload, Key) ->
|
||||||
%% @private
|
Bin = term_to_binary(Payload),
|
||||||
%% Gracefully shut down a socket, logging (but sidestepping) the case when the socket
|
Signed = <<Command:8, Bin/binary>>,
|
||||||
%% has already been closed by the other side.
|
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
|
done(Socket) ->
|
||||||
ok ->
|
ok = inet:setopts(Socket, [{active, once}]),
|
||||||
log(info, "Disconnected from ~tp", [Socket]);
|
receive
|
||||||
{error, Error} ->
|
{tcp, Socket, <<0:1, 0:7>>} ->
|
||||||
Message = "Shutdown connection ~p failed with: ~p",
|
zx_net:disconnect(Socket);
|
||||||
log(warning, Message, [Socket, Error])
|
{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.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
done(Socket, Reason) ->
|
||||||
%%% Error exits
|
ok = zx_net:disconnect(Socket),
|
||||||
|
case is_binary(Reason) of
|
||||||
-spec error_exit(Error, Line) -> no_return()
|
true -> {error, zx_net:err_in(Reason)};
|
||||||
when Error :: term(),
|
false -> {error, Reason}
|
||||||
Line :: non_neg_integer().
|
end.
|
||||||
%% @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).
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
-license("GPL-3.0").
|
-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/1, stop/1]).
|
||||||
-export([start_link/1, init/2]).
|
-export([start_link/1, init/2]).
|
||||||
|
|
||||||
@ -38,31 +38,17 @@ unsubscribe(Conn, Package) ->
|
|||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
-spec fetch(Conn, ID, Object) -> ok
|
-spec request(Conn, ID, Action) -> 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
|
|
||||||
when Conn :: pid(),
|
when Conn :: pid(),
|
||||||
ID :: zx_daemon:id(),
|
ID :: zx_daemon:id(),
|
||||||
Action :: zx_daemon:action().
|
Action :: zx_daemon:action().
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Wraps any legal query.
|
%% Wraps any legal request.
|
||||||
%% Only to be called by zx_daemon.
|
%% Only to be called by zx_daemon.
|
||||||
%% Results must be returned with the ID via zx_daemon:result/2.
|
%% Results must be returned with the ID via zx_daemon:result/2.
|
||||||
|
|
||||||
query(Conn, ID, Action) ->
|
request(Conn, ID, Action) ->
|
||||||
Conn ! {query, ID, Action},
|
Conn ! {request, ID, Action},
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
@ -109,7 +95,6 @@ start_link(Target) ->
|
|||||||
%% gen_server callback. For more information refer to the OTP documentation.
|
%% gen_server callback. For more information refer to the OTP documentation.
|
||||||
|
|
||||||
init(Parent, Target) ->
|
init(Parent, Target) ->
|
||||||
ok = log(info, "Connecting to ~tp", [Target]),
|
|
||||||
Debug = sys:debug_options([]),
|
Debug = sys:debug_options([]),
|
||||||
ok = proc_lib:init_ack(Parent, {ok, self()}),
|
ok = proc_lib:init_ack(Parent, {ok, self()}),
|
||||||
connect(Parent, Debug, Target).
|
connect(Parent, Debug, Target).
|
||||||
@ -124,12 +109,12 @@ init(Parent, Target) ->
|
|||||||
Target :: zx:host().
|
Target :: zx:host().
|
||||||
|
|
||||||
connect(Parent, Debug, {Host, Port}) ->
|
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
|
case gen_tcp:connect(Host, Port, Options, 5000) of
|
||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
confirm_service(Parent, Debug, Socket);
|
confirm_service(Parent, Debug, Socket);
|
||||||
{error, Error} ->
|
{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),
|
ok = zx_daemon:report(failed),
|
||||||
terminate()
|
terminate()
|
||||||
end.
|
end.
|
||||||
@ -144,11 +129,13 @@ connect(Parent, Debug, {Host, Port}) ->
|
|||||||
%% another node.
|
%% another node.
|
||||||
|
|
||||||
confirm_service(Parent, Debug, Socket) ->
|
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
|
receive
|
||||||
{tcp, Socket, <<0:8, RealmsBin/binary>>} ->
|
{tcp, Socket, <<0:8, RealmsBin/binary>>} ->
|
||||||
{ok, Realms} = zx_lib:b_to_ts(RealmsBin),
|
{ok, Available} = zx_lib:b_to_ts(RealmsBin),
|
||||||
ok = zx_daemon:report({connected, Realms}),
|
ok = zx_daemon:report({connected, Available}),
|
||||||
loop(Parent, Debug, Socket);
|
loop(Parent, Debug, Socket);
|
||||||
{tcp, Socket, <<1:8, HostsBin/binary>>} ->
|
{tcp, Socket, <<1:8, HostsBin/binary>>} ->
|
||||||
{ok, Hosts} = zx_lib:b_to_ts(HostsBin),
|
{ok, Hosts} = zx_lib:b_to_ts(HostsBin),
|
||||||
@ -198,29 +185,26 @@ loop(Parent, Debug, Socket) ->
|
|||||||
ok = zx_daemon:report({serial_update, Realm, Serial}),
|
ok = zx_daemon:report({serial_update, Realm, Serial}),
|
||||||
loop(Parent, Debug, Socket);
|
loop(Parent, Debug, Socket);
|
||||||
{tcp, Socket, Unexpected} ->
|
{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),
|
ok = zx_net:disconnect(Socket),
|
||||||
terminate();
|
terminate();
|
||||||
|
{request, ID, Action} ->
|
||||||
|
Result = dispatch(Socket, ID, Action),
|
||||||
|
ok = zx_daemon:result(ID, Result),
|
||||||
|
loop(Parent, Debug, Socket);
|
||||||
{subscribe, Package} ->
|
{subscribe, Package} ->
|
||||||
ok = do_subscribe(Socket, Package),
|
ok = do_subscribe(Socket, Package),
|
||||||
loop(Parent, Debug, Socket);
|
loop(Parent, Debug, Socket);
|
||||||
{unsubscribe, Package} ->
|
{unsubscribe, Package} ->
|
||||||
ok = do_unsubscribe(Socket, Package),
|
ok = do_unsubscribe(Socket, Package),
|
||||||
loop(Parent, Debug, Socket);
|
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 ->
|
stop ->
|
||||||
ok = zx_net:disconnect(Socket),
|
ok = zx_net:disconnect(Socket),
|
||||||
terminate();
|
terminate();
|
||||||
{tcp_closed, Socket} ->
|
{tcp_closed, Socket} ->
|
||||||
handle_unexpected_close();
|
handle_unexpected_close();
|
||||||
Unexpected ->
|
Unexpected ->
|
||||||
ok = log(warning, "Unexpected message: ~tp", [Unexpected]),
|
ok = log(warning, "Unexpected message: ~160tp", [Unexpected]),
|
||||||
loop(Parent, Debug, Socket)
|
loop(Parent, Debug, Socket)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -235,7 +219,7 @@ handle_package_update(Sig, Bin) ->
|
|||||||
true ->
|
true ->
|
||||||
zx_daemon:notify(Package, {update, PackageID});
|
zx_daemon:notify(Package, {update, PackageID});
|
||||||
false ->
|
false ->
|
||||||
ok = log(error, "Received an unverified update message: ~tp", [Message]),
|
ok = log(error, "Received an unverified update message: ~160tp", [Message]),
|
||||||
terminate()
|
terminate()
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -260,26 +244,33 @@ do_unsubscribe(Socket, Package) ->
|
|||||||
wait_ok(Socket).
|
wait_ok(Socket).
|
||||||
|
|
||||||
|
|
||||||
do_query(Socket, {list, Realm}) ->
|
-spec wait_ok(gen_tcp:socket()) -> ok | no_return().
|
||||||
Reference = term_to_binary(Realm),
|
|
||||||
Message = <<0:1, 3:7, Reference/binary>>,
|
wait_ok(Socket) ->
|
||||||
send_query(Socket, Message);
|
receive
|
||||||
do_query(Socket, {list, Realm, Name}) ->
|
{tcp, Socket, <<0:1, 0:7>>} -> ok
|
||||||
Reference = term_to_binary({Realm, Name}),
|
after 5000 -> handle_timeout(Socket)
|
||||||
Message = <<0:1, 4:7, Reference/binary>>,
|
end.
|
||||||
send_query(Socket, Message);
|
|
||||||
do_query(Socket, {list, Realm, Name, Version}) ->
|
|
||||||
Reference = term_to_binary({Realm, Name, Version}),
|
dispatch(Socket, ID, Action) ->
|
||||||
Message = <<0:1, 4:7, Reference/binary>>,
|
case Action of
|
||||||
send_query(Socket, Message);
|
{list, R} ->
|
||||||
do_query(Socket, {latest, Realm, Name}) ->
|
send_query(Socket, <<0:1, 3:7, (term_to_binary(R))/binary>>);
|
||||||
Reference = term_to_binary({Realm, Name}),
|
{list, R, N} ->
|
||||||
Message = <<0:1, 6:7, Reference/binary>>,
|
send_query(Socket, <<0:1, 4:7, (term_to_binary({R, N}))/binary>>);
|
||||||
send_query(Socket, Message);
|
{list, R, N, V} ->
|
||||||
do_query(Socket, {latest, Realm, Name, Version}) ->
|
send_query(Socket, <<0:1, 4:7, (term_to_binary({R, N, V}))/binary>>);
|
||||||
Reference = term_to_binary({Realm, Name, Version}),
|
{latest, R, N} ->
|
||||||
Message = <<0:1, 6:7, Reference/binary>>,
|
send_query(Socket, <<0:1, 5:7, (term_to_binary({R, N}))/binary>>);
|
||||||
send_query(Socket, Message).
|
{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) ->
|
send_query(Socket, Message) ->
|
||||||
@ -290,105 +281,67 @@ send_query(Socket, Message) ->
|
|||||||
wait_query(Socket) ->
|
wait_query(Socket) ->
|
||||||
ok = inet:setopts(Socket, [{active, once}]),
|
ok = inet:setopts(Socket, [{active, once}]),
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, <<1:1, 0:7>>} ->
|
{tcp, Socket, <<1:1, 0:7>>} -> wait_pong(Socket);
|
||||||
ok = pong(Socket),
|
{tcp, Socket, <<0:1, 0:7>>} -> ok;
|
||||||
wait_query(Socket);
|
{tcp, Socket, <<0:1, 0:7, Bin/binary>>} -> zx_lib:b_to_ts(Bin);
|
||||||
{tcp, Socket, <<0:1, 0:7, Bin/binary>>} ->
|
{tcp, Socket, Bin} -> {error, zx_net:err_in(Bin)}
|
||||||
{ok, Result} = zx_lib:b_to_ts(Bin),
|
after 5000 -> handle_timeout(Socket)
|
||||||
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)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
wait_pong(Socket) ->
|
||||||
|
ok = pong(Socket),
|
||||||
|
wait_query(Socket).
|
||||||
|
|
||||||
|
|
||||||
pong(Socket) ->
|
pong(Socket) ->
|
||||||
gen_tcp:send(Socket, <<1:1, 0:7>>).
|
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(),
|
when Socket :: gen_tcp:socket(),
|
||||||
ID :: zx_daemon:id(),
|
ID :: zx_daemon:id(),
|
||||||
PackageID :: zx:package_id(),
|
PackageID :: zx:package_id(),
|
||||||
Result :: ok.
|
Result :: {done, binary()}.
|
||||||
%% @private
|
%% @private
|
||||||
%% Download a package to the local cache.
|
%% Download a package to the local cache.
|
||||||
|
|
||||||
do_fetch(Socket, ID, PackageID) ->
|
make_fetch(Socket, ID, PackageID) ->
|
||||||
Reference = binary_to_term(PackageID),
|
TermBin = term_to_binary(PackageID),
|
||||||
Message = <<0:1, 7:7, Reference/binary>>,
|
Message = <<0:1, 6:7, TermBin/binary>>,
|
||||||
ok = gen_tcp:send(Socket, Message),
|
ok = gen_tcp:send(Socket, Message),
|
||||||
ok = wait_hops(Socket, ID),
|
ok = wait_hops(Socket, ID),
|
||||||
{ok, Bin} = receive_zsp(Socket),
|
{ok, Bin} = zx_net:rx(Socket),
|
||||||
zx_daemon:result(ID, {done, Bin}).
|
{done, Bin}.
|
||||||
|
|
||||||
|
|
||||||
wait_hops(Socket, ID) ->
|
wait_hops(Socket, ID) ->
|
||||||
ok = inet:setopts(Socket, [{active, once}]),
|
ok = inet:setopts(Socket, [{active, once}]),
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, <<0:1, 0:7, 0:8>>} ->
|
{tcp, Socket, <<0:1, 0:7, 0:8>>} ->
|
||||||
ok = inet:setopts(Socket, [{packet, 0}]),
|
ok;
|
||||||
gen_tcp:send(Socket, <<0:1, 0:7>>);
|
|
||||||
{tcp, Socket, <<0:1, 0:7, Distance:8>>} ->
|
{tcp, Socket, <<0:1, 0:7, Distance:8>>} ->
|
||||||
ok = zx_daemon:result(ID, {hops, Distance}),
|
ok = zx_daemon:result(ID, {hops, Distance}),
|
||||||
wait_hops(Socket, ID);
|
wait_hops(Socket, ID);
|
||||||
{tcp, Socket, <<0:1, 1:7>>} ->
|
|
||||||
{error, bad_message};
|
|
||||||
{tcp, Socket, <<0:1, 2:7>>} ->
|
{tcp, Socket, <<0:1, 2:7>>} ->
|
||||||
{error, bad_realm};
|
handle_timeout(Socket);
|
||||||
{tcp, Socket, <<0:1, 3:7>>} ->
|
{tcp, Socket, Bin} ->
|
||||||
{error, bad_package};
|
Reason = zx_net:err_in(Bin),
|
||||||
{tcp, Socket, <<0:1, 4:7>>} ->
|
ok = log(error, "Failed in wait_hops/2 with reason: ~tp", [Reason]),
|
||||||
{error, bad_version};
|
ok = zx_net:disconnect(Socket),
|
||||||
{tcp, Socket, <<0:1, 5:7>>} ->
|
terminate()
|
||||||
handle_timeout(Socket)
|
|
||||||
after 60000 ->
|
after 60000 ->
|
||||||
handle_timeout(Socket)
|
handle_timeout(Socket)
|
||||||
end.
|
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
|
%%% Terminal handlers
|
||||||
|
|
||||||
-spec handle_unexpected_close() -> no_return().
|
-spec handle_unexpected_close() -> no_return().
|
||||||
|
|
||||||
handle_unexpected_close() ->
|
handle_unexpected_close() ->
|
||||||
|
ok = log(info, "Connection closed unexpectedly."),
|
||||||
ok = zx_daemon:report(disconnected),
|
ok = zx_daemon:report(disconnected),
|
||||||
terminate().
|
terminate().
|
||||||
|
|
||||||
@ -401,15 +354,6 @@ handle_timeout(Socket) ->
|
|||||||
terminate().
|
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().
|
-spec terminate() -> no_return().
|
||||||
%% @private
|
%% @private
|
||||||
%% Convenience wrapper around the suicide call.
|
%% Convenience wrapper around the suicide call.
|
||||||
|
|||||||
@ -144,11 +144,11 @@
|
|||||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
-license("GPL-3.0").
|
-license("GPL-3.0").
|
||||||
|
|
||||||
|
-export([declare_proxy/0]).
|
||||||
-export([pass_meta/3,
|
-export([pass_meta/3,
|
||||||
subscribe/1, unsubscribe/1,
|
subscribe/1, unsubscribe/1,
|
||||||
list/0, list/1, list/2, list/3, latest/1,
|
list/0, list/1, list/2, list/3, latest/1,
|
||||||
verify_key/1, fetch/1, install/1,
|
verify_key/1, fetch/1, install/1, build/1]).
|
||||||
pending/1, packagers/1, maintainers/1, sysops/1]).
|
|
||||||
-export([report/1, result/2, notify/2]).
|
-export([report/1, result/2, notify/2]).
|
||||||
-export([start_link/0, init_connections/0, stop/0]).
|
-export([start_link/0, init_connections/0, stop/0]).
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
@ -158,7 +158,6 @@
|
|||||||
-export_type([id/0, result/0,
|
-export_type([id/0, result/0,
|
||||||
realm_list/0, package_list/0, version_list/0, latest_result/0,
|
realm_list/0, package_list/0, version_list/0, latest_result/0,
|
||||||
fetch_result/0, key_result/0,
|
fetch_result/0, key_result/0,
|
||||||
pending_result/0, pack_result/0, maint_result/0, sysop_result/0,
|
|
||||||
sub_message/0]).
|
sub_message/0]).
|
||||||
|
|
||||||
|
|
||||||
@ -199,11 +198,11 @@
|
|||||||
|
|
||||||
|
|
||||||
-record(conn,
|
-record(conn,
|
||||||
{pid :: pid(),
|
{pid = none :: none | pid(),
|
||||||
host :: zx:host(),
|
host = none :: none | zx:host(),
|
||||||
realms :: [zx:realm()],
|
realms = [] :: [zx:realm()],
|
||||||
requests :: [id()],
|
requests = [] :: [id()],
|
||||||
subs :: [{pid(), zx:package()}]}).
|
subs = [] :: [{pid(), zx:package()}]}).
|
||||||
|
|
||||||
|
|
||||||
%% State Types
|
%% State Types
|
||||||
@ -237,6 +236,8 @@
|
|||||||
| {list, zx:realm(), zx:name(), zx:version()}
|
| {list, zx:realm(), zx:name(), zx:version()}
|
||||||
| {latest, zx:realm(), zx:name()}
|
| {latest, zx:realm(), zx:name()}
|
||||||
| {latest, zx:realm(), zx:name(), zx:version()}
|
| {latest, zx:realm(), zx:name(), zx:version()}
|
||||||
|
| {pending, zx:realm(), zx:name()}
|
||||||
|
| {approved, zx:realm()}
|
||||||
| {fetch, zx:realm(), zx:name(), zx:version()}
|
| {fetch, zx:realm(), zx:name(), zx:version()}
|
||||||
| {fetchkey, zx:realm(), zx:key_name()}.
|
| {fetchkey, zx:realm(), zx:key_name()}.
|
||||||
|
|
||||||
@ -254,11 +255,7 @@
|
|||||||
| version_list()
|
| version_list()
|
||||||
| latest_result()
|
| latest_result()
|
||||||
| fetch_result()
|
| fetch_result()
|
||||||
| key_result()
|
| key_result()}.
|
||||||
| pending_result()
|
|
||||||
| pack_result()
|
|
||||||
| maint_result()
|
|
||||||
| sysop_result()}.
|
|
||||||
|
|
||||||
-type realm_list() :: [zx:realm()].
|
-type realm_list() :: [zx:realm()].
|
||||||
-type package_list() :: {ok, [zx:name()]}
|
-type package_list() :: {ok, [zx:name()]}
|
||||||
@ -274,7 +271,7 @@
|
|||||||
| bad_version
|
| bad_version
|
||||||
| timeout}.
|
| timeout}.
|
||||||
-type fetch_result() :: {hops, non_neg_integer()}
|
-type fetch_result() :: {hops, non_neg_integer()}
|
||||||
| done
|
| {done, zx:package_id()}
|
||||||
| {error, bad_realm
|
| {error, bad_realm
|
||||||
| bad_package
|
| bad_package
|
||||||
| bad_version
|
| bad_version
|
||||||
@ -283,21 +280,6 @@
|
|||||||
| {error, bad_realm
|
| {error, bad_realm
|
||||||
| bad_key
|
| bad_key
|
||||||
| timeout}.
|
| 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
|
% Subscription Results
|
||||||
@ -306,6 +288,13 @@
|
|||||||
Message :: {update, zx:package_id()}
|
Message :: {update, zx:package_id()}
|
||||||
| {error, bad_realm | bad_package}}.
|
| {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
|
%%% Requestor Interface
|
||||||
|
|
||||||
@ -320,9 +309,6 @@
|
|||||||
%% references.
|
%% references.
|
||||||
|
|
||||||
pass_meta(Meta, Dir, ArgV) ->
|
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}).
|
gen_server:cast(?MODULE, {pass_meta, Meta, Dir, ArgV}).
|
||||||
|
|
||||||
|
|
||||||
@ -460,6 +446,15 @@ verify_key({Realm, KeyName}) ->
|
|||||||
request({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().
|
-spec install(Path :: file:filename()) -> zx:outcome().
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Install a package from a local file.
|
%% Install a package from a local file.
|
||||||
@ -468,83 +463,10 @@ install(Path) ->
|
|||||||
gen_server:call(?MODULE, {install, Path}).
|
gen_server:call(?MODULE, {install, Path}).
|
||||||
|
|
||||||
|
|
||||||
-spec fetch(PackageString :: string()) -> {ok, id()}.
|
-spec build(zx:package_id()) -> zx:outcome().
|
||||||
%% @doc
|
|
||||||
%% Install the specified package. This returns an id() that will be referenced
|
|
||||||
%% in a later response message.
|
|
||||||
|
|
||||||
fetch(PackageString) ->
|
build(PackageID) ->
|
||||||
case zx_lib:package_id(PackageString) of
|
gen_server:call(?MODULE, {build, PackageID}).
|
||||||
{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}).
|
|
||||||
|
|
||||||
|
|
||||||
%% Request Caster
|
%% Request Caster
|
||||||
@ -655,8 +577,12 @@ handle_call({install, Path}, _, State) ->
|
|||||||
Result = do_import_zsp(Path),
|
Result = do_import_zsp(Path),
|
||||||
NewState = eval_queue(State),
|
NewState = eval_queue(State),
|
||||||
{reply, Result, NewState};
|
{reply, Result, NewState};
|
||||||
|
handle_call({build, PackageID}, _, State) ->
|
||||||
|
Result = do_build(PackageID),
|
||||||
|
NewState = eval_queue(State),
|
||||||
|
{reply, Result, NewState};
|
||||||
handle_call(Unexpected, From, State) ->
|
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}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
@ -692,7 +618,7 @@ handle_cast(init_connections, State) ->
|
|||||||
handle_cast(stop, State) ->
|
handle_cast(stop, State) ->
|
||||||
{stop, normal, State};
|
{stop, normal, State};
|
||||||
handle_cast(Unexpected, State) ->
|
handle_cast(Unexpected, State) ->
|
||||||
ok = log(warning, "Unexpected cast: ~tp", [Unexpected]),
|
ok = log(warning, "Unexpected cast: ~160tp", [Unexpected]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
@ -703,7 +629,7 @@ handle_info({'DOWN', Ref, process, Pid, Reason}, State) ->
|
|||||||
NewState = clear_monitor(Pid, Ref, Reason, State),
|
NewState = clear_monitor(Pid, Ref, Reason, State),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
handle_info(Unexpected, State) ->
|
handle_info(Unexpected, State) ->
|
||||||
ok = log(warning, "Unexpected info: ~tp", [Unexpected]),
|
ok = log(warning, "Unexpected info: ~160tp", [Unexpected]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
@ -726,7 +652,7 @@ terminate(normal, #s{cx = CX}) ->
|
|||||||
ok ->
|
ok ->
|
||||||
log(info, "Cache written.");
|
log(info, "Cache written.");
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
Message = "Cache write failed with ~tp",
|
Message = "Cache write failed with ~160tp",
|
||||||
log(error, Message, [Reason])
|
log(error, Message, [Reason])
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -742,9 +668,6 @@ terminate(normal, #s{cx = CX}) ->
|
|||||||
NewState :: state().
|
NewState :: state().
|
||||||
|
|
||||||
do_pass_meta(Meta, Home, ArgV, 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}.
|
State#s{meta = Meta, home = Home, argv = ArgV}.
|
||||||
|
|
||||||
|
|
||||||
@ -756,9 +679,10 @@ do_pass_meta(Meta, Home, ArgV, State) ->
|
|||||||
%% @private
|
%% @private
|
||||||
%% Enqueue a subscription request.
|
%% 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],
|
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
|
-spec do_unsubscribe(Pid, Package, State) -> NextState
|
||||||
@ -769,9 +693,10 @@ do_subscribe(Pid, Package, State = #s{actions = Actions}) ->
|
|||||||
%% @private
|
%% @private
|
||||||
%% Clear or dequeue a subscription request.
|
%% 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],
|
NewActions = [{unsubscribe, Pid, Package} | Actions],
|
||||||
State#s{actions = NewActions}.
|
State#s{actions = NewActions, mx = NewMX}.
|
||||||
|
|
||||||
|
|
||||||
-spec do_request(Requestor, Action, State) -> NextState
|
-spec do_request(Requestor, Action, State) -> NextState
|
||||||
@ -782,9 +707,10 @@ do_unsubscribe(Pid, Package, State = #s{actions = Actions}) ->
|
|||||||
%% @private
|
%% @private
|
||||||
%% Enqueue requests and update relevant index.
|
%% 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],
|
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
|
-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),
|
NewMX = mx_del_monitor(Conn, conn, MX),
|
||||||
disconnected(Conn, State#s{mx = NewMX});
|
disconnected(Conn, State#s{mx = NewMX});
|
||||||
do_report(Conn, timeout, State = #s{mx = MX}) ->
|
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),
|
NewMX = mx_del_monitor(Conn, conn, MX),
|
||||||
disconnected(Conn, State#s{mx = NewMX}).
|
disconnected(Conn, State#s{mx = NewMX}).
|
||||||
|
|
||||||
@ -1024,11 +950,11 @@ handle_fetch_result(ID, Outcome, {Requestor, _}, Requests, MX) ->
|
|||||||
handle_orphan_result(ID, Result, Dropped) ->
|
handle_orphan_result(ID, Result, Dropped) ->
|
||||||
case maps:take(ID, Dropped) of
|
case maps:take(ID, Dropped) of
|
||||||
{Request, NewDropped} ->
|
{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]),
|
ok = log(info, Message, [ID, Request, Result]),
|
||||||
NewDropped;
|
NewDropped;
|
||||||
error ->
|
error ->
|
||||||
Message = "Received untracked request result ~tp: ~tp",
|
Message = "Received untracked request result ~160tp: ~160tp",
|
||||||
ok = log(warning, Message, [ID, Result]),
|
ok = log(warning, Message, [ID, Result]),
|
||||||
Dropped
|
Dropped
|
||||||
end.
|
end.
|
||||||
@ -1080,49 +1006,38 @@ eval_queue(State = #s{actions = Actions}) ->
|
|||||||
eval_queue([], State) ->
|
eval_queue([], State) ->
|
||||||
State;
|
State;
|
||||||
eval_queue([Action = {request, Pid, ID, Message} | Rest],
|
eval_queue([Action = {request, Pid, ID, Message} | Rest],
|
||||||
State = #s{actions = Actions, requests = Requests, mx = MX, cx = CX}) ->
|
State = #s{actions = Actions, requests = Requests, cx = CX}) ->
|
||||||
{NewActions, NewRequests, NewMX, NewCX} =
|
{NewActions, NewRequests, NewCX} =
|
||||||
case dispatch_request(Message, ID, CX) of
|
case dispatch_request(Message, ID, CX) of
|
||||||
{dispatched, NextCX} ->
|
{dispatched, NextCX} ->
|
||||||
NextRequests = maps:put(ID, {Pid, Message}, Requests),
|
NextRequests = maps:put(ID, {Pid, Message}, Requests),
|
||||||
NextMX = mx_add_monitor(Pid, requestor, MX),
|
{Actions, NextRequests, NextCX};
|
||||||
{Actions, NextRequests, NextMX, NextCX};
|
|
||||||
{result, Response} ->
|
|
||||||
Pid ! Response,
|
|
||||||
{Actions, Requests, MX, CX};
|
|
||||||
wait ->
|
wait ->
|
||||||
NextActions = [Action | Actions],
|
NextActions = [Action | Actions],
|
||||||
NextMX = mx_add_monitor(Pid, requestor, MX),
|
{NextActions, Requests, CX};
|
||||||
{NextActions, Requests, NextMX, CX}
|
Result ->
|
||||||
|
Pid ! Result,
|
||||||
|
{Actions, Requests, CX}
|
||||||
end,
|
end,
|
||||||
NewState =
|
NewState = State#s{actions = NewActions, requests = NewRequests, cx = NewCX},
|
||||||
State#s{actions = NewActions,
|
|
||||||
requests = NewRequests,
|
|
||||||
mx = NewMX,
|
|
||||||
cx = NewCX},
|
|
||||||
eval_queue(Rest, NewState);
|
eval_queue(Rest, NewState);
|
||||||
eval_queue([Action = {subscribe, Pid, Package} | Rest],
|
eval_queue([Action = {subscribe, Pid, Package} | Rest],
|
||||||
State = #s{actions = Actions, mx = MX, cx = CX}) ->
|
State = #s{actions = Actions, cx = CX}) ->
|
||||||
{NewActions, NewMX, NewCX} =
|
{NewActions, NewCX} =
|
||||||
case cx_add_sub(Pid, Package, CX) of
|
case cx_add_sub(Pid, Package, CX) of
|
||||||
{need_sub, Conn, NextCX} ->
|
{need_sub, Conn, NextCX} ->
|
||||||
ok = zx_conn:subscribe(Conn, Package),
|
ok = zx_conn:subscribe(Conn, Package),
|
||||||
NextMX = mx_add_monitor(Pid, subscriber, MX),
|
{Actions, NextCX};
|
||||||
{Actions, NextMX, NextCX};
|
|
||||||
{have_sub, NextCX} ->
|
{have_sub, NextCX} ->
|
||||||
NextMX = mx_add_monitor(Pid, subscriber, MX),
|
{Actions, NextCX};
|
||||||
{Actions, NextMX, NextCX};
|
|
||||||
unassigned ->
|
unassigned ->
|
||||||
NextMX = mx_add_monitor(Pid, subscriber, MX),
|
{[Action | Actions], CX};
|
||||||
{[Action | Actions], NextMX, CX};
|
|
||||||
unconfigured ->
|
unconfigured ->
|
||||||
Pid ! {z_sub, Package, {error, bad_realm}},
|
Pid ! {z_sub, Package, {error, bad_realm}},
|
||||||
{Actions, MX, CX}
|
{Actions, CX}
|
||||||
end,
|
end,
|
||||||
eval_queue(Rest, State#s{actions = NewActions, mx = NewMX, cx = NewCX});
|
eval_queue(Rest, State#s{actions = NewActions, cx = NewCX});
|
||||||
eval_queue([{unsubscribe, Pid, Package} | Rest],
|
eval_queue([{unsubscribe, Pid, Package} | Rest], State = #s{cx = CX}) ->
|
||||||
State = #s{mx = MX, cx = CX}) ->
|
|
||||||
{ok, NewMX} = mx_del_monitor(Pid, {subscription, Package}, MX),
|
|
||||||
NewCX =
|
NewCX =
|
||||||
case cx_del_sub(Pid, Package, CX) of
|
case cx_del_sub(Pid, Package, CX) of
|
||||||
{{drop_sub, ConnPid}, NextCX} ->
|
{{drop_sub, ConnPid}, NextCX} ->
|
||||||
@ -1133,11 +1048,11 @@ eval_queue([{unsubscribe, Pid, Package} | Rest],
|
|||||||
unassigned ->
|
unassigned ->
|
||||||
CX;
|
CX;
|
||||||
unconfigured ->
|
unconfigured ->
|
||||||
Message = "Received 'unsubscribe' request for unconfigured realm: ~tp",
|
M = "Received 'unsubscribe' request for unconfigured realm: ~160tp",
|
||||||
ok = log(warning, Message, [Package]),
|
ok = log(warning, M, [Package]),
|
||||||
CX
|
CX
|
||||||
end,
|
end,
|
||||||
eval_queue(Rest, State#s{mx = NewMX, cx = NewCX}).
|
eval_queue(Rest, State#s{cx = NewCX}).
|
||||||
|
|
||||||
|
|
||||||
-spec dispatch_request(Action, ID, CX) -> Result
|
-spec dispatch_request(Action, ID, CX) -> Result
|
||||||
@ -1194,7 +1109,7 @@ clear_monitor(Pid,
|
|||||||
disconnected(Pid, State#s{mx = NewMX});
|
disconnected(Pid, State#s{mx = NewMX});
|
||||||
{{Reqs, Subs}, NewMX} ->
|
{{Reqs, Subs}, NewMX} ->
|
||||||
NewActions = drop_actions(Pid, Actions),
|
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),
|
NewCX = cx_clear_client(Pid, Reqs, Subs, CX),
|
||||||
State#s{actions = NewActions,
|
State#s{actions = NewActions,
|
||||||
requests = NewRequests,
|
requests = NewRequests,
|
||||||
@ -1203,7 +1118,7 @@ clear_monitor(Pid,
|
|||||||
cx = NewCX};
|
cx = NewCX};
|
||||||
unknown ->
|
unknown ->
|
||||||
Unexpected = {'DOWN', Ref, process, Pid, Reason},
|
Unexpected = {'DOWN', Ref, process, Pid, Reason},
|
||||||
ok = log(warning, "Unexpected info: ~tp", [Unexpected]),
|
ok = log(warning, "Unexpected info: ~160tp", [Unexpected]),
|
||||||
State
|
State
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -1237,7 +1152,7 @@ drop_requests(ReqIDs, Dropped, Requests) ->
|
|||||||
NewDrop = maps:put(K, V, Drop),
|
NewDrop = maps:put(K, V, Drop),
|
||||||
{NewDrop, NewKeep}
|
{NewDrop, NewKeep}
|
||||||
end,
|
end,
|
||||||
lists:fold(Partition, {Dropped, Requests}, ReqIDs).
|
lists:foldl(Partition, {Dropped, Requests}, ReqIDs).
|
||||||
|
|
||||||
|
|
||||||
-spec do_fetch(PackageID, Requestor, State) -> NewState
|
-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),
|
Path = zx_lib:zsp_path(PackageID),
|
||||||
case file:read_file(Path) of
|
case file:read_file(Path) of
|
||||||
{ok, Bin} ->
|
{ok, Bin} ->
|
||||||
case do_import_package(Bin) of
|
ok = do_fetch2(Bin, Requestor, ID),
|
||||||
ok ->
|
{ok, State};
|
||||||
Requestor ! {result, ID, ok},
|
|
||||||
{ok, State};
|
|
||||||
Error ->
|
|
||||||
Requestor ! {result, ID, Error},
|
|
||||||
{ok, State}
|
|
||||||
end;
|
|
||||||
{error, enoent} ->
|
{error, enoent} ->
|
||||||
{Realm, Name, Version} = PackageID,
|
{Realm, Name, Version} = PackageID,
|
||||||
Action = {fetch, Realm, Name, Version},
|
Action = {fetch, Realm, Name, Version},
|
||||||
@ -1268,6 +1177,15 @@ do_fetch(PackageID, Requestor, State = #s{id = ID}) ->
|
|||||||
Requestor ! {result, ID, Error}
|
Requestor ! {result, ID, Error}
|
||||||
end.
|
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().
|
-spec do_import_zsp(file:filename()) -> zx:outcome().
|
||||||
%% @private
|
%% @private
|
||||||
@ -1276,15 +1194,9 @@ do_fetch(PackageID, Requestor, State = #s{id = ID}) ->
|
|||||||
%% madness with a "try++" here by spawning a suicidal helper.
|
%% madness with a "try++" here by spawning a suicidal helper.
|
||||||
|
|
||||||
do_import_zsp(Path) ->
|
do_import_zsp(Path) ->
|
||||||
{Pid, Mon} = spawn_monitor(fun() -> import_from_path(Path) end),
|
case file:read_file(Path) of
|
||||||
receive
|
{ok, Bin} -> do_import_package(Bin);
|
||||||
{Pid, Outcome} ->
|
Error -> Error
|
||||||
true = demonitor(Mon, [flush]),
|
|
||||||
Outcome;
|
|
||||||
{'DOWN', Pid, process, Mon, Info} ->
|
|
||||||
{error, Info}
|
|
||||||
after 5000 ->
|
|
||||||
{error, timeout}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -1301,8 +1213,7 @@ do_import_package(Bin) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec import_from_path(ZspPath) -> no_return()
|
-spec import_package(binary()) -> no_return().
|
||||||
when ZspPath :: file:filename().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% The happy path of .zsp installation.
|
%% The happy path of .zsp installation.
|
||||||
%% Must NEVER be executed by the zx_daemon directly.
|
%% 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
|
%% 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.
|
%% 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>>) ->
|
import_package(Bin = <<Size:24, Sig:Size/binary, Signed/binary>>) ->
|
||||||
<<MetaSize:16, MetaBin:MetaSize/binary, TarGZ/binary>> = Signed,
|
<<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}),
|
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
|
||||||
true = zx_key:verify(Signed, Sig, PubKey),
|
true = zx_key:verify(Signed, Sig, PubKey),
|
||||||
ok = file:write_file(zx_lib:zsp_path(PackageID), Bin),
|
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}.
|
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
|
%%% Monitor Index ADT Interface Functions
|
||||||
|
|
||||||
@ -1359,29 +1295,29 @@ mx_new() ->
|
|||||||
|
|
||||||
-spec mx_add_monitor(Pid, Category, MX) -> NewMX
|
-spec mx_add_monitor(Pid, Category, MX) -> NewMX
|
||||||
when Pid :: pid(),
|
when Pid :: pid(),
|
||||||
Category :: subscriber
|
Category :: {requestor, id()}
|
||||||
| requestor
|
| {subscriber, Sub :: tuple()}
|
||||||
| attempt,
|
| attempt,
|
||||||
MX :: monitor_index(),
|
MX :: monitor_index(),
|
||||||
NewMX :: monitor_index().
|
NewMX :: monitor_index().
|
||||||
%% @private
|
%% @private
|
||||||
%% Begin monitoring the given Pid, keeping track of its category.
|
%% 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
|
case maps:take(Pid, MX) of
|
||||||
{{Ref, {Subs, Reqs}}, NextMX} ->
|
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||||
maps:put(Pid, {Ref, {Subs + 1, Reqs}}, NextMX);
|
maps:put(Pid, {Ref, {Reqs, [Sub | Subs]}}, NextMX);
|
||||||
error ->
|
error ->
|
||||||
Ref = monitor(process, Pid),
|
Ref = monitor(process, Pid),
|
||||||
maps:put(Pid, {Ref, {1, 0}}, MX)
|
maps:put(Pid, {Ref, {[], [Sub]}}, MX)
|
||||||
end;
|
end;
|
||||||
mx_add_monitor(Pid, requestor, MX) ->
|
mx_add_monitor(Pid, {requestor, Req}, MX) ->
|
||||||
case maps:take(Pid, MX) of
|
case maps:take(Pid, MX) of
|
||||||
{{Ref, {Subs, Reqs}}, NextMX} ->
|
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||||
maps:put(Pid, {Ref, {Subs, Reqs + 1}}, NextMX);
|
maps:put(Pid, {Ref, {[Req | Reqs], Subs}}, NextMX);
|
||||||
error ->
|
error ->
|
||||||
Ref = monitor(process, Pid),
|
Ref = monitor(process, Pid),
|
||||||
maps:put(Pid, {Ref, {0, 1}}, MX)
|
maps:put(Pid, {Ref, {[Req], []}}, MX)
|
||||||
end;
|
end;
|
||||||
mx_add_monitor(Pid, attempt, MX) ->
|
mx_add_monitor(Pid, attempt, MX) ->
|
||||||
false = maps:is_key(Pid, MX),
|
false = maps:is_key(Pid, MX),
|
||||||
@ -1422,13 +1358,13 @@ mx_del_monitor(Pid, conn, MX) ->
|
|||||||
{{Ref, conn}, NewMX} = maps:take(Pid, MX),
|
{{Ref, conn}, NewMX} = maps:take(Pid, MX),
|
||||||
true = demonitor(Ref, [flush]),
|
true = demonitor(Ref, [flush]),
|
||||||
NewMX;
|
NewMX;
|
||||||
mx_del_monitor(Pid, {requestor, ID}, MX) ->
|
mx_del_monitor(Pid, {requestor, Req}, MX) ->
|
||||||
case maps:take(Pid, MX) of
|
case maps:take(Pid, MX) of
|
||||||
{{Ref, {[ID], []}}, NextMX} ->
|
{{Ref, {[Req], []}}, NextMX} ->
|
||||||
true = demonitor(Ref, [flush]),
|
true = demonitor(Ref, [flush]),
|
||||||
NextMX;
|
NextMX;
|
||||||
{{Ref, {Reqs, Subs}}, NextMX} when Reqs > 0 ->
|
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||||
NewReqs = lists:delete(ID, Reqs),
|
NewReqs = lists:delete(Req, Reqs),
|
||||||
maps:put(Pid, {Ref, {NewReqs, Subs}}, NextMX)
|
maps:put(Pid, {Ref, {NewReqs, Subs}}, NextMX)
|
||||||
end;
|
end;
|
||||||
mx_del_monitor(Pid, {subscriber, Sub}, MX) ->
|
mx_del_monitor(Pid, {subscriber, Sub}, MX) ->
|
||||||
@ -1436,7 +1372,7 @@ mx_del_monitor(Pid, {subscriber, Sub}, MX) ->
|
|||||||
{{Ref, {[], [Sub]}}, NextMX} ->
|
{{Ref, {[], [Sub]}}, NextMX} ->
|
||||||
true = demonitor(Ref, [flush]),
|
true = demonitor(Ref, [flush]),
|
||||||
NextMX;
|
NextMX;
|
||||||
{{Ref, {Reqs, Subs}}, NextMX} when Subs > 0 ->
|
{{Ref, {Reqs, Subs}}, NextMX} ->
|
||||||
NewSubs = lists:delete(Sub, Subs),
|
NewSubs = lists:delete(Sub, Subs),
|
||||||
maps:put(Pid, {Ref, {Reqs, NewSubs}}, NextMX)
|
maps:put(Pid, {Ref, {Reqs, NewSubs}}, NextMX)
|
||||||
end.
|
end.
|
||||||
@ -1484,7 +1420,7 @@ cx_load() ->
|
|||||||
{ok, Realms} ->
|
{ok, Realms} ->
|
||||||
#cx{realms = maps:from_list(Realms)};
|
#cx{realms = maps:from_list(Realms)};
|
||||||
{error, Reason} ->
|
{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(error, Message, [Reason]),
|
||||||
ok = log(warning, "No realms configured."),
|
ok = log(warning, "No realms configured."),
|
||||||
#cx{}
|
#cx{}
|
||||||
@ -1524,7 +1460,7 @@ cx_populate(Realm, CX) ->
|
|||||||
Record = cx_load_realm_meta(Meta),
|
Record = cx_load_realm_meta(Meta),
|
||||||
[{Realm, Record} | CX];
|
[{Realm, Record} | CX];
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
Message = "Loading realm ~tp failed with: ~tp. Skipping...",
|
Message = "Loading realm ~160tp failed with: ~160tp. Skipping...",
|
||||||
ok = log(warning, Message, [Realm, Reason]),
|
ok = log(warning, Message, [Realm, Reason]),
|
||||||
CX
|
CX
|
||||||
end.
|
end.
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
-license("GPL-3.0").
|
-license("GPL-3.0").
|
||||||
|
|
||||||
-export([ensure_keypair/1, have_key/2, path/2,
|
-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]).
|
load/2, verify/3]).
|
||||||
|
|
||||||
-include("zx_logger.hrl").
|
-include("zx_logger.hrl").
|
||||||
@ -30,15 +30,15 @@ ensure_keypair(KeyID = {Realm, KeyName}) ->
|
|||||||
{true, true} ->
|
{true, true} ->
|
||||||
true;
|
true;
|
||||||
{false, 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]),
|
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||||
{error, Message, 2};
|
{error, Message, 2};
|
||||||
{true, false} ->
|
{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]),
|
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||||
{error, Message, 2};
|
{error, Message, 2};
|
||||||
{false, false} ->
|
{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]),
|
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||||
{error, Message, 2}
|
{error, Message, 2}
|
||||||
end.
|
end.
|
||||||
@ -70,11 +70,11 @@ path(private, {Realm, KeyName}) ->
|
|||||||
|
|
||||||
%%% Key generation
|
%%% Key generation
|
||||||
|
|
||||||
-spec prompt_keygen() -> zx:key_id().
|
-spec prompt_keygen(zx:user_id()) -> zx:key_name().
|
||||||
%% @private
|
%% @private
|
||||||
%% Prompt the user for a valid KeyPrefix to use for naming a new RSA keypair.
|
%% Prompt the user for a valid KeyPrefix to use for naming a new RSA keypair.
|
||||||
|
|
||||||
prompt_keygen() ->
|
prompt_keygen(UserID = {Realm, UserName}) ->
|
||||||
Message =
|
Message =
|
||||||
"~nKEY NAME~n"
|
"~nKEY NAME~n"
|
||||||
"Enter a name for your new key pair.~n"
|
"Enter a name for your new key pair.~n"
|
||||||
@ -83,13 +83,15 @@ prompt_keygen() ->
|
|||||||
"consecutive underscores. (That is: [a-z0-9_])~n"
|
"consecutive underscores. (That is: [a-z0-9_])~n"
|
||||||
" Example: my_key~n",
|
" Example: my_key~n",
|
||||||
ok = io:format(Message),
|
ok = io:format(Message),
|
||||||
Input = zx_tty:get_input(),
|
KeyTag = zx_tty:get_input(),
|
||||||
case zx_lib:valid_lower0_9(Input) of
|
case zx_lib:valid_lower0_9(KeyTag) of
|
||||||
true ->
|
true ->
|
||||||
Input;
|
KeyName = UserName ++ "-" ++ KeyTag,
|
||||||
|
ok = zx_key:generate_rsa({Realm, KeyName}),
|
||||||
|
KeyName;
|
||||||
false ->
|
false ->
|
||||||
ok = io:format("Bad key name ~tp. Try again.~n", [Input]),
|
ok = io:format("Bad key name ~ts. Try again.~n", [KeyTag]),
|
||||||
prompt_keygen()
|
prompt_keygen(UserID)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ generate_rsa(KeyID, BaseName) ->
|
|||||||
PemFile = BaseName ++ ".pub.pem",
|
PemFile = BaseName ++ ".pub.pem",
|
||||||
KeyFile = path(private, KeyID),
|
KeyFile = path(private, KeyID),
|
||||||
PubFile = path(public, 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
|
case gen_p_key(KeyFile) of
|
||||||
ok ->
|
ok ->
|
||||||
ok = der_to_pem(KeyFile, PemFile),
|
ok = der_to_pem(KeyFile, PemFile),
|
||||||
@ -252,7 +254,6 @@ load(Type, KeyID) ->
|
|||||||
public -> 'RSAPublicKey'
|
public -> 'RSAPublicKey'
|
||||||
end,
|
end,
|
||||||
Path = path(Type, KeyID),
|
Path = path(Type, KeyID),
|
||||||
ok = log(info, "Loading key from file ~ts", [Path]),
|
|
||||||
case file:read_file(Path) of
|
case file:read_file(Path) of
|
||||||
{ok, Bin} -> {ok, public_key:der_decode(DerType, Bin)};
|
{ok, Bin} -> {ok, public_key:der_decode(DerType, Bin)};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
|
|||||||
@ -25,16 +25,19 @@
|
|||||||
valid_lower0_9/1, valid_label/1, valid_version/1,
|
valid_lower0_9/1, valid_label/1, valid_version/1,
|
||||||
string_to_version/1, version_to_string/1,
|
string_to_version/1, version_to_string/1,
|
||||||
package_id/1, package_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,
|
find_latest_compatible/2, installed/1,
|
||||||
realm_conf/1, load_realm_conf/1,
|
realm_conf/1, load_realm_conf/1,
|
||||||
build/0,
|
build/0,
|
||||||
rm_rf/1, rm/1,
|
rm_rf/1, rm/1,
|
||||||
|
time_diff/2, elapsed_time/1,
|
||||||
b_to_t/1, b_to_ts/1]).
|
b_to_t/1, b_to_ts/1]).
|
||||||
|
|
||||||
-include("zx_logger.hrl").
|
-include("zx_logger.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
-type core_dir() :: etc | var | tmp | log | key | zsp | lib.
|
||||||
|
|
||||||
|
|
||||||
%%% Functions
|
%%% Functions
|
||||||
|
|
||||||
@ -70,13 +73,7 @@ find_zomp_dir() ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec path(Type) -> Path
|
-spec path(core_dir()) -> file:filename().
|
||||||
when Type :: etc
|
|
||||||
| var
|
|
||||||
| tmp
|
|
||||||
| log
|
|
||||||
| lib,
|
|
||||||
Path :: file:filename().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Return the top-level path of the given type in the Zomp/ZX system.
|
%% 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").
|
path(lib) -> filename:join(zomp_dir(), "lib").
|
||||||
|
|
||||||
|
|
||||||
-spec path(Type, Realm) -> Path
|
-spec path(core_dir(), zx:realm()) -> file:filename().
|
||||||
when Type :: etc
|
|
||||||
| var
|
|
||||||
| tmp
|
|
||||||
| log
|
|
||||||
| lib,
|
|
||||||
Realm :: zx:realm(),
|
|
||||||
Path :: file:filename().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Return the realm-level path of the given type in the Zomp/ZX system.
|
%% 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).
|
filename:join(path(Type), Realm).
|
||||||
|
|
||||||
|
|
||||||
-spec path(Type, Realm, Name) -> Path
|
-spec path(core_dir(), zx:realm(), zx:name()) -> file:filename().
|
||||||
when Type :: etc
|
|
||||||
| var
|
|
||||||
| tmp
|
|
||||||
| log
|
|
||||||
| lib,
|
|
||||||
Realm :: zx:realm(),
|
|
||||||
Name :: zx:name(),
|
|
||||||
Path :: file:filename().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Return the package-level path of the given type in the Zomp/ZX system.
|
%% 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]).
|
filename:join([path(Type), Realm, Name]).
|
||||||
|
|
||||||
|
|
||||||
-spec path(Type, Realm, Name, Version) -> Path
|
-spec path(core_dir(), zx:realm(), zx:name(), zx:version()) -> file:filename().
|
||||||
when Type :: etc
|
|
||||||
| var
|
|
||||||
| tmp
|
|
||||||
| log
|
|
||||||
| lib,
|
|
||||||
Realm :: zx:realm(),
|
|
||||||
Name :: zx:name(),
|
|
||||||
Version :: zx:version(),
|
|
||||||
Path :: file:filename().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Return the version-specific level path of the given type in the Zomp/ZX system.
|
%% 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]).
|
filename:join([path(Type), Realm, Name, VersionString]).
|
||||||
|
|
||||||
|
|
||||||
-spec ppath(Type, PackageID) -> Path
|
-spec ppath(core_dir(), zx:package_id()) -> file:filename().
|
||||||
when Type :: etc
|
|
||||||
| var
|
|
||||||
| tmp
|
|
||||||
| log
|
|
||||||
| lib,
|
|
||||||
PackageID :: zx:package_id(),
|
|
||||||
Path :: file:filename().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% An alias for path/4, but more convenient when needing a path from a closed
|
%% An alias for path/4, but more convenient when needing a path from a closed
|
||||||
%% package_id().
|
%% package_id().
|
||||||
@ -257,7 +223,7 @@ read_project_meta(Dir) ->
|
|||||||
{error, enoent} ->
|
{error, enoent} ->
|
||||||
{error, "No project zomp.meta file. Wrong directory? Not initialized?", 2};
|
{error, "No project zomp.meta file. Wrong directory? Not initialized?", 2};
|
||||||
Error ->
|
Error ->
|
||||||
ok = log(error, "Read from zomp.meta failed with: ~tp", [Error]),
|
ok = log(error, "Read from zomp.meta failed with: ~tw", [Error]),
|
||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -327,7 +293,7 @@ exec_shell(CMD) ->
|
|||||||
ok;
|
ok;
|
||||||
Out ->
|
Out ->
|
||||||
Trimmed = string:trim(Out, trailing, "\r\n"),
|
Trimmed = string:trim(Out, trailing, "\r\n"),
|
||||||
log(info, "os:cmd(~tp) -> ~ts", [CMD, Trimmed])
|
log(info, "os:cmd(~tw) -> ~ts", [CMD, Trimmed])
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -335,8 +301,7 @@ exec_shell(CMD) ->
|
|||||||
%% @private
|
%% @private
|
||||||
%% Check whether a provided string is a valid lower0_9.
|
%% Check whether a provided string is a valid lower0_9.
|
||||||
|
|
||||||
valid_lower0_9([Char | Rest])
|
valid_lower0_9([Char | Rest]) when $a =< Char, Char =< $z ->
|
||||||
when $a =< Char, Char =< $z ->
|
|
||||||
valid_lower0_9(Rest, Char);
|
valid_lower0_9(Rest, Char);
|
||||||
valid_lower0_9(_) ->
|
valid_lower0_9(_) ->
|
||||||
false.
|
false.
|
||||||
@ -363,8 +328,7 @@ valid_lower0_9(_, _) ->
|
|||||||
%% @private
|
%% @private
|
||||||
%% Check whether a provided string is a valid label.
|
%% Check whether a provided string is a valid label.
|
||||||
|
|
||||||
valid_label([Char | Rest])
|
valid_label([Char | Rest]) when $a =< Char, Char =< $z ->
|
||||||
when $a =< Char, Char =< $z ->
|
|
||||||
valid_label(Rest, Char);
|
valid_label(Rest, Char);
|
||||||
valid_label(_) ->
|
valid_label(_) ->
|
||||||
false.
|
false.
|
||||||
@ -582,13 +546,13 @@ package_string(_) ->
|
|||||||
{error, invalid_package_id}.
|
{error, invalid_package_id}.
|
||||||
|
|
||||||
|
|
||||||
-spec namify_zsp(PackageID) -> ZrpFileName
|
-spec zsp_name(PackageID) -> ZrpFileName
|
||||||
when PackageID :: zx:package_id(),
|
when PackageID :: zx:package_id(),
|
||||||
ZrpFileName :: file:filename().
|
ZrpFileName :: file:filename().
|
||||||
%% @private
|
%% @private
|
||||||
%% Map a PackageID to its correct .zsp package file name.
|
%% Map a PackageID to its correct .zsp package file name.
|
||||||
|
|
||||||
namify_zsp(PackageID) ->
|
zsp_name(PackageID) ->
|
||||||
{ok, PackageString} = package_string(PackageID),
|
{ok, PackageString} = package_string(PackageID),
|
||||||
PackageString ++ ".zsp".
|
PackageString ++ ".zsp".
|
||||||
|
|
||||||
@ -596,7 +560,7 @@ namify_zsp(PackageID) ->
|
|||||||
-spec zsp_path(zx:package_id()) -> file:filename().
|
-spec zsp_path(zx:package_id()) -> file:filename().
|
||||||
|
|
||||||
zsp_path(PackageID = {Realm, _, _}) ->
|
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
|
-spec find_latest_compatible(Version, Versions) -> Result
|
||||||
@ -675,7 +639,7 @@ load_realm_conf(Realm) ->
|
|||||||
{ok, C} ->
|
{ok, C} ->
|
||||||
{ok, maps:from_list(C)};
|
{ok, maps:from_list(C)};
|
||||||
Error ->
|
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
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -732,6 +696,25 @@ rm(Path) ->
|
|||||||
end.
|
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.
|
-spec b_to_t(binary()) -> {ok, term()} | error.
|
||||||
%% @private
|
%% @private
|
||||||
%% A wrapper for the binary_to_term/1 BIF to hide the try..catch mess in the places we
|
%% 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) ->
|
b_to_ts(Binary) ->
|
||||||
try
|
try
|
||||||
binary_to_term(Binary, [safe])
|
{ok, binary_to_term(Binary, [safe])}
|
||||||
catch
|
catch
|
||||||
error:badarg -> error
|
error:badarg -> error
|
||||||
end.
|
end.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -9,11 +9,48 @@
|
|||||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
-license("GPL-3.0").
|
-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").
|
-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
|
-spec disconnect(Socket) -> ok
|
||||||
when Socket :: gen_tcp:socket().
|
when Socket :: gen_tcp:socket().
|
||||||
%% @doc
|
%% @doc
|
||||||
@ -21,12 +58,12 @@
|
|||||||
%% disconnected on the other side.
|
%% disconnected on the other side.
|
||||||
|
|
||||||
disconnect(Socket) ->
|
disconnect(Socket) ->
|
||||||
case zomp:peername(Socket) of
|
case peername(Socket) of
|
||||||
{ok, {Addr, Port}} ->
|
{ok, {Addr, Port}} ->
|
||||||
Host = inet:ntoa(Addr),
|
Host = inet:ntoa(Addr),
|
||||||
disconnect(Socket, Host, Port);
|
disconnect(Socket, Host, Port);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
log(warning, "Disconnect failed with: ~p", [Reason])
|
log(warning, "Disconnect failed with: ~w", [Reason])
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -38,17 +75,182 @@ disconnect(Socket) ->
|
|||||||
disconnect(Socket, Host, Port) ->
|
disconnect(Socket, Host, Port) ->
|
||||||
case gen_tcp:shutdown(Socket, read_write) of
|
case gen_tcp:shutdown(Socket, read_write) of
|
||||||
ok ->
|
ok ->
|
||||||
log(info, "~ts:~w disconnected", [Host, Port]);
|
ok;
|
||||||
{error, enotconn} ->
|
{error, enotconn} ->
|
||||||
log(info, "~ts:~w disconnected", [Host, Port]),
|
|
||||||
receive
|
receive
|
||||||
{tcp_closed, Socket} -> ok
|
{tcp_closed, Socket} -> ok
|
||||||
after 0 -> ok
|
after 0 -> ok
|
||||||
end;
|
end;
|
||||||
{error, E} ->
|
{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
|
receive
|
||||||
{tcp_closed, Socket} -> ok
|
{tcp_closed, Socket} -> ok
|
||||||
after 0 -> ok
|
after 0 -> ok
|
||||||
end
|
end
|
||||||
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} ->
|
{ok, List} ->
|
||||||
populate_data(List);
|
populate_data(List);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
ok = log(error, "Load ~tp failed with: ~tp", [Path, Reason]),
|
ok = log(error, "Load ~160tp failed with: ~160tp", [Path, Reason]),
|
||||||
Data = #d{},
|
Data = #d{},
|
||||||
ok = save(Data),
|
ok = save(Data),
|
||||||
Data
|
Data
|
||||||
@ -203,7 +203,7 @@ maxconn(Value, Data) when is_integer(Value) and Value > 0 ->
|
|||||||
Data#d{maxconn = Value}.
|
Data#d{maxconn = Value}.
|
||||||
|
|
||||||
|
|
||||||
-spec managed(data()) -> list(zx:realm()).
|
-spec managed(data()) -> [zx:realm()].
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Return the list of realms managed by the current node.
|
%% 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
|
case zx_lib:realm_exists(Realm) of
|
||||||
true ->
|
true ->
|
||||||
NewData = Data#d{managed = sets:add_element(Realm, Managed)},
|
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};
|
{ok, NewData};
|
||||||
false ->
|
false ->
|
||||||
ok = log(warning, "Cannot manage unconfigured realm: ~tp", [Realm]),
|
ok = log(warning, "Cannot manage unconfigured realm: ~160tp", [Realm]),
|
||||||
{error, unconfigured}
|
{error, unconfigured}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -262,10 +262,10 @@ rem_managed(Realm, Data = #d{managed = Managed}) ->
|
|||||||
case sets:is_element(Realm, Managed) of
|
case sets:is_element(Realm, Managed) of
|
||||||
true ->
|
true ->
|
||||||
NewData = Data#d{managed = sets:del_element(Realm, Managed)},
|
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};
|
{ok, NewData};
|
||||||
false ->
|
false ->
|
||||||
ok = log(warning, "Cannot stop managing unmanaged realm: ~tp", [Realm]),
|
ok = log(warning, "Cannot stop managing unmanaged realm: ~160tp", [Realm]),
|
||||||
{error, unmanaged}
|
{error, unmanaged}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,10 @@
|
|||||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
-license("GPL-3.0").
|
-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
|
%%% Type Definitions
|
||||||
@ -32,12 +35,12 @@ get_input() ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec get_input(Prompt :: string()) -> string().
|
-spec get_input(Info :: string()) -> string().
|
||||||
%% @private
|
%% @private
|
||||||
%% Introduce
|
%% Prompt the user for input.
|
||||||
|
|
||||||
get_input(Prompt) ->
|
get_input(Info) ->
|
||||||
get_input(Prompt, []).
|
get_input(Info, []).
|
||||||
|
|
||||||
|
|
||||||
-spec get_input(Format, Args) -> string()
|
-spec get_input(Format, Args) -> string()
|
||||||
@ -46,14 +49,55 @@ get_input(Prompt) ->
|
|||||||
%% @private
|
%% @private
|
||||||
%% Allow the caller to use io format strings and args to create a prompt.
|
%% Allow the caller to use io format strings and args to create a prompt.
|
||||||
|
|
||||||
|
|
||||||
get_input(Format, Args) ->
|
get_input(Format, Args) ->
|
||||||
Prompt = io_lib:format(Format, Args),
|
get_input(Format, Args, []).
|
||||||
case string:trim(io:get_line(["(or \"QUIT\") ", Prompt, ": "])) of
|
|
||||||
|
|
||||||
|
-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();
|
"QUIT" -> what_a_quitter();
|
||||||
String -> String
|
String -> String
|
||||||
end.
|
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
|
-spec select(Options) -> Selected
|
||||||
when Options :: [option()],
|
when Options :: [option()],
|
||||||
Selected :: term().
|
Selected :: term().
|
||||||
@ -139,3 +183,9 @@ hurr() -> io:format("That isn't an option.~n").
|
|||||||
what_a_quitter() ->
|
what_a_quitter() ->
|
||||||
ok = io:format("User abort: \"QUIT\".~nHalting.~n"),
|
ok = io:format("User abort: \"QUIT\".~nHalting.~n"),
|
||||||
halt(0).
|
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