diff --git a/zomp/etc/version.txt b/zomp/etc/version.txt index 6e8bf73..0ea3a94 100644 --- a/zomp/etc/version.txt +++ b/zomp/etc/version.txt @@ -1 +1 @@ -0.1.0 +0.2.0 diff --git a/zomp/lib/otpr/zx/0.1.0/include/zx_logger.hrl b/zomp/lib/otpr/zx/0.1.0/include/zx_logger.hrl deleted file mode 100644 index 393e720..0000000 --- a/zomp/lib/otpr/zx/0.1.0/include/zx_logger.hrl +++ /dev/null @@ -1,36 +0,0 @@ --export([log/2, log/3]). - - --spec log(Level, Format) -> ok - when Level :: info - | warning - | error, - Format :: string(). -%% @private -%% @equiv log(Level, Format, []) - -log(Level, Format) -> - log(Level, Format, []). - - --spec log(Level, Format, Args) -> ok - when Level :: info - | warning - | error, - Format :: string(), - Args :: [term()]. -%% @private -%% A logging abstraction to hide whatever logging back end is actually in use. -%% Format must adhere to Erlang format string rules, and the arity of Args must match -%% the provided format. - -log(Level, Format, Args) -> - Tag = - case Level of - info -> "[INFO]"; - warning -> "[WARNING]"; - error -> "[ERROR]" - end, - Out = io_lib:format("~s ~w ~w: " ++ Format ++ "~n", [Tag, ?MODULE, self() | Args]), - UTF8 = unicode:characters_to_binary(Out), - io:format(UTF8). diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_auth.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_auth.erl deleted file mode 100644 index 146c66e..0000000 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_auth.erl +++ /dev/null @@ -1,598 +0,0 @@ -%%% @doc -%%% ZX Auth -%%% -%%% This module is where all the AUTH type command code lives. AUTH commands are special -%%% because they do not involve the zx_daemon at all, though they do perform network -%%% operations. -%%% -%%% All AUTH procedures terminate the runtime once complete. -%%% @end - --module(zx_auth). --author("Craig Everett "). --copyright("Craig Everett "). --license("GPL-3.0"). - --export([list_pending/1, list_approved/1, - submit/1, review/1, approve/1, reject/1, accept/1, - list_users/1, list_packagers/1, list_maintainers/1, list_sysops/1, - add_user/1, - add_packager/2, rem_packager/2, - add_maintainer/2, rem_maintainer/2, - add_sysop/1, - add_package/1]). - --include("zx_logger.hrl"). - - - -%%% Functions - - --spec list_pending(PackageName :: string()) -> zx:outcome(). -%% @private -%% List the versions of a package that are pending review. The package name is input by -%% the user as a string of the form "otpr-zomp" and the output is a list of full -%% package IDs, printed one per line to stdout (like "otpr-zomp-3.2.2"). - -list_pending(PackageName) -> - case zx_lib:package_id(PackageName) of - {ok, {Realm, Name, {z, z, z}}} -> list_pending2(Realm, Name); - Error -> Error - end. - -list_pending2(Realm, Name) -> - case connect(Realm) of - {ok, Socket} -> list_pending3(Realm, Name, Socket); - Error -> Error - end. - -list_pending3(Realm, Name, Socket) -> - Message = <<"ZOMP AUTH 1:", 0:24, 5:8, (term_to_binary({Realm, Name}))/binary>>, - ok = gen_tcp:send(Socket, Message), - receive - {tcp, Socket, <<0:8, Bin/binary>>} -> list_pending4(Realm, Name, Socket, Bin); - {tcp, Socket, Error} -> done(Socket, Error); - {tcp_closed, Socket} -> {error, "Socket closed unexpectedly."} - after 5000 -> done(Socket, timeout) - end. - -list_pending4(Realm, Name, Socket, Bin) -> - ok = zx_net:disconnect(Socket), - case zx_lib:b_to_ts(Bin) of - {ok, Versions} -> list_pending5(Realm, Name, Versions); - Error -> Error - end. - -list_pending5(Realm, Name, Versions) -> - Print = - fun(Version) -> - PackageID = {Realm, Name, Version}, - {ok, PackageString} = zx_lib:package_string(PackageID), - io:format("~ts~n", [PackageString]) - end, - lists:foreach(Print, Versions). - - --spec list_approved(zx:realm()) -> zx:outcome(). -%% @private -%% List the package ids of all packages waiting in the resign queue for the given realm, -%% printed to stdout one per line. - -list_approved(Realm) -> - case connect(Realm) of - {ok, Socket} -> list_approved2(Realm, Socket); - Error -> Error - end. - -list_approved2(Realm, Socket) -> - Message = <<"ZOMP AUTH 1:", 0:24, 7:8, (term_to_binary(Realm))/binary>>, - ok = gen_tcp:send(Socket, Message), - receive - {tcp, Socket, <<0:8, Bin/binary>>} -> list_approved3(Socket, Bin, Realm); - {tcp, Socket, Error} -> done(Socket, Error); - {tcp_closed, Socket} -> {error, "Socket closed unexpectedly."} - after 5000 -> done(Socket, timeout) - end. - -list_approved3(Socket, Bin, Realm) -> - ok = zx_net:disconnect(Socket), - case zx_lib:b_to_ts(Bin) of - {ok, PackageIDs} -> list_approved4(Realm, PackageIDs); - Error -> Error - end. - -list_approved4(Realm, PackageIDs) -> - Print = - fun({Name, Version}) -> - {ok, PackageString} = zx_lib:package_string({Realm, Name, Version}), - io:format("~ts~n", [PackageString]) - end, - lists:foreach(Print, PackageIDs). - - --spec submit(ZspPath :: file:filename()) -> zx:outcome(). -%% @private -%% Submit a package to the appropriate "prime" server for the given realm. - -submit(ZspPath) -> - case file:read_file(ZspPath) of - {ok, ZspBin} -> submit2(ZspBin); - Error -> Error - end. - -submit2(ZspBin = <>) -> - <> = 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) -> - case zx_lib:package_id(PackageString) of - {ok, PackageID} -> review2(PackageID); - Error -> Error - end. - -review2(PackageID = {Realm, _, _}) -> - case connect(Realm) of - {ok, Socket} -> review3(PackageID, Socket); - Error -> Error - end. - -review3(PackageID, Socket) -> - Command = 8, - TermBin = term_to_binary(PackageID), - Request = <<0:24, Command:8, TermBin/binary>>, - ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>), - receive - {tcp, Socket, <<0:1, 0:7>>} -> review4(PackageID, Socket); - {tcp, Socket, Bin} -> done(Socket, Bin); - {tcp_closed, Socket} -> {error, tcp_closed} - after 5000 -> done(Socket, timeout) - end. - -review4(PackageID, Socket) -> - case zx_net:rx(Socket) of - {ok, <>} -> - ok = zx_net:disconnect(Socket), - review5(PackageID, Sig, Signed); - Error -> - done(Socket, Error) - end. - -review5(PackageID, Sig, Signed = <>) -> - 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. - - -review7(PackageID, SigKey, Signed, Sig, TgzBin) -> - case zx_key:verify(Signed, Sig, SigKey) of - true -> review8(PackageID, TgzBin); - false -> {error, bad_sig} - end. - -review8(PackageID, TgzBin) -> - {ok, PackageString} = zx_lib:package_string(PackageID), - case file:make_dir(PackageString) of - ok -> - review9(PackageString, TgzBin); - {error, Error} -> - Message = "Creating dir ./~ts failed with ~ts. Aborting.", - ok = log(error, Message, [PackageString, Error]), - {error, Error} - end. - -review9(PackageString, TgzBin) -> - ok = erl_tar:extract({binary, TgzBin}, [compressed, {cwd, PackageString}]), - log(info, "Sources unpacked to ./~ts", [PackageString]). - - -approve(Package) -> package_operation(9, Package). - -reject(Package) -> package_operation(10, Package). - - --spec package_operation(Code :: 9 | 10, Package :: string()) -> zx:outcome(). - -package_operation(Code, Package) -> - case zx_lib:package_id(Package) of - {ok, {Realm, Name, Version}} -> make_su_request(Code, Realm, {Name, Version}); - Error -> Error - end. - - --spec accept(PackageString :: string()) -> zx:outcome(). - -accept(PackageString) -> - case zx_lib:package_id(PackageString) of - {ok, PackageID} -> accept2(PackageID); - Error -> Error - end. - -accept2(PackageID = {Realm, Name, Version}) -> - case connect_auth(Realm) of - {ok, AuthConn} -> accept3(PackageID, {Name, Version}, AuthConn); - Error -> Error - end. - -accept3(PackageID, PV, {UserName, KeyName, Key, Tag, Socket}) -> - Command = 11, - Payload = {Tag, UserName, KeyName, PV}, - Request = pack_and_sign(Command, Payload, Key), - ok = gen_tcp:send(Socket, <<0:8, Request/binary>>), - ok = inet:setopts(Socket, [{active, once}]), - receive - {tcp, Socket, <<0:1, 0:7>>} -> accept4(PackageID, KeyName, Key, Socket); - {tcp, Socket, Bin} -> done(Socket, Bin); - {tcp_closed, Socket} -> {error, tcp_closed} - after 5000 -> done(Socket, timeout) - end. - -accept4(PackageID, KeyName, Key, Socket) -> - case zx_net:rx(Socket) of - {ok, <>} -> - accept5(PackageID, {KeyName, Key, Socket}, Sig, Signed); - Error -> - done(Socket, Error) - end. - -accept5(PackageID, - Auth, - Sig, - Signed = <>) -> - 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 = <>, - Sig = public_key:sign(SignMe, sha512, Key), - SigSize = byte_size(Sig), - ZspData = <>, - ok = zx_net:tx(Socket, ZspData), - done(Socket). - - -list_users(Realm) -> - list_users(2, Realm). - -list_packagers(Package) -> - {ok, {Realm, Name, {z, z, z}}} = zx_lib:package_id(Package), - list_users(3, {Realm, Name}). - -list_maintainers(Package) -> - {ok, {Realm, Name, {z, z, z}}} = zx_lib:package_id(Package), - list_users(4, {Realm, Name}). - -list_sysops(Realm) -> - list_users(5, Realm). - - --spec list_users(Command, Target) -> zx:outcome() - when Command :: 2..5, - Target :: zx:realm() | zx:package(). - -list_users(Command, Target) -> - case make_uu_request(Command, Target) of - {ok, Users} -> lists:foreach(fun print_user/1, Users); - Error -> Error - end. - -print_user({UserName, RealName, [{"email", Email}]}) -> - io:format("~ts (~ts <~ts>) ~n", [UserName, RealName, Email]). - - --spec add_user(file:filename()) -> zx:outcome(). - -add_user(ZPUF) -> - case file:read_file(ZPUF) of - {ok, Bin} -> add_user2(Bin); - Error -> Error - end. - -add_user2(Bin) -> - case zx_lib:b_to_t(Bin) of - {ok, {UserInfo, KeyData}} -> - Realm = proplists:get_value(realm, UserInfo), - UserData = {proplists:get_value(username, UserInfo), - proplists:get_value(realname, UserInfo), - proplists:get_value(contact_info, UserInfo), - [setelement(2, KD, none) || KD <- KeyData]}, - Command = 13, - make_su_request(Command, Realm, UserData); - error -> - {error, "Bad user file.", 1} - end. - - -add_packager(Package, User) -> user_auth_operation(15, Package, User). - -rem_packager(Package, User) -> user_auth_operation(16, Package, User). - -add_maintainer(Package, User) -> user_auth_operation(17, Package, User). - -rem_maintainer(Package, User) -> user_auth_operation(18, Package, User). - - --spec user_auth_operation(Code, Package, User)-> zx:outcome() - when Code :: 15..18, - Package :: string(), - User :: zx:user_name(). - -user_auth_operation(Code, Package, User) -> - case zx_lib:package_id(Package) of - {ok, {Realm, Name, {z, z, z}}} -> make_su_request(Code, Realm, {Name, User}); - Error -> Error - end. - - --spec add_sysop(file:filename()) -> zx:outcome(). - -add_sysop(UserFile) -> - ok = log(info, "Would add ~ts to sysop list.", [UserFile]), - {error, "Not yet implemented", 1}. - - --spec add_package(zx:package()) -> zx:outcome(). - -add_package(PackageName) -> - ok = file:set_cwd(zx_lib:zomp_dir()), - case zx_lib:package_id(PackageName) of - {ok, {Realm, Name, {z, z, z}}} -> add_package2(Realm, Name); - Error -> Error - end. - -add_package2(Realm, Name) -> - case connect_auth(Realm) of - {ok, AuthConn} -> add_package3(Realm, Name, AuthConn); - Error -> Error - end. - -add_package3(Realm, Name, {UserName, KeyName, Key, Tag, Socket}) -> - Command = 12, - Package = {Realm, Name}, - Payload = {Tag, UserName, KeyName, Package}, - Request = pack_and_sign(Command, Payload, Key), - ok = gen_tcp:send(Socket, <<0:8, Request/binary>>), - done(Socket). - - - -%%% Generic Request Forms - --spec make_uu_request(Command, Target) -> zx:outcome() - when Command :: pos_integer(), - Target :: zx:realm() | zx:package() | zx:package_id(). - -make_uu_request(Command, Target) when is_tuple(Target) -> - make_uu_request2(Command, element(1, Target), Target); -make_uu_request(Command, Target) when is_list(Target) -> - make_uu_request2(Command, Target, Target). - -make_uu_request2(Command, Realm, Target) -> - case connect(Realm) of - {ok, Socket} -> make_uu_request3(Command, Target, Socket); - Error -> Error - end. - -make_uu_request3(Command, Target, Socket) -> - TermBin = term_to_binary(Target), - Request = <<"ZOMP AUTH 1:", 0:24, Command:8, TermBin/binary>>, - ok = gen_tcp:send(Socket, Request), - ok = inet:setopts(Socket, [{active, once}]), - receive - {tcp, Socket, <<0:1, 0:7, Bin/binary>>} -> make_uu_request4(Bin); - {tcp, Socket, Bin} -> done(Socket, Bin); - {tcp_closed, Socket} -> {error, tcp_closed} - after 5000 -> done(Socket, timeout) - end. - -make_uu_request4(Bin) -> - case zx_lib:b_to_ts(Bin) of - error -> {error, bad_response}; - Term -> Term - end. - - --spec make_su_request(Command, Realm, Data) -> zx:outcome() - when Command :: 1 | 9 | 10 | 13..20, - Realm :: zx:realm(), - Data :: term(). - -make_su_request(Command, Realm, Data) -> - AuthData = prep_auth(Realm), - case connect(Realm) of - {ok, Socket} -> make_su_request2(Command, Realm, Data, AuthData, Socket); - Error -> Error - end. - -make_su_request2(Command, Realm, Data, {Signatory, KeyName, Key}, Socket) -> - Payload = {Realm, Signatory, KeyName, Data}, - Request = pack_and_sign(Command, Payload, Key), - ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>), - done(Socket). - - - -%%% Connectiness with prime - --spec connect_auth(Realm) -> Result - when Realm :: zx:realm(), - Result :: {ok, AuthConn} - | {error, Reason}, - AuthConn :: {UserName :: zx:user_name(), - KeyName :: zx:key_name(), - Key :: term(), - SSTag :: zx:ss_tag(), - Socket :: gen_tcp:socket()}, - Reason :: term(). -%% @private -%% Connect to one of the servers in the realm constellation. - -connect_auth(Realm) -> - UserData = prep_auth(Realm), - case zx_lib:load_realm_conf(Realm) of - {ok, RealmConf} -> - connect_auth2(Realm, RealmConf, UserData); - Error -> - ok = log(error, "Realm ~160tp is not configured.", [Realm]), - Error - end. - -connect_auth2(Realm, RealmConf, UserData) -> - {Host, Port} = maps:get(prime, RealmConf), - Options = [{packet, 4}, {mode, binary}, {active, once}], - case gen_tcp:connect(Host, Port, Options, 5000) of - {ok, Socket} -> - connect_auth3(Socket, Realm, UserData); - Error = {error, E} -> - ok = log(warning, "Connection problem: ~160tp", [E]), - {error, Error} - end. - -connect_auth3(Socket, Realm, UD = {UserName, KeyName, Key}) -> - Null = 0, - Timestamp = calendar:universal_time(), - Payload = {Realm, Timestamp, UserName, KeyName}, - NullRequest = pack_and_sign(Null, Payload, Key), - ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", NullRequest/binary>>), - receive - {tcp, Socket, <<0:8, Bin/binary>>} -> connect_auth4(Socket, UD, Bin); - {tcp, Socket, Bin} -> done(Socket, Bin); - {tcp_closed, Socket} -> {error, tcp_closed} - after 5000 -> done(Socket, timeout) - end. - -connect_auth4(Socket, {UserName, KeyName, Key}, Bin) -> - case zx_lib:b_to_ts(Bin) of - {ok, Tag} -> {ok, {UserName, KeyName, Key, Tag, Socket}}; - error -> done(Socket, bad_response) - end. - - -connect(Realm) -> - case zx_lib:load_realm_conf(Realm) of - {ok, RealmConf} -> - {Host, Port} = maps:get(prime, RealmConf), - Options = [{packet, 4}, {mode, binary}, {nodelay, true}, {active, once}], - gen_tcp:connect(Host, Port, Options, 5000); - Error -> - ok = log(error, "Realm ~160tp is not configured.", [Realm]), - Error - end. - - --spec prep_auth(Realm) -> {User, KeyName, Key} - when Realm :: zx:realm(), - User :: zx:user_id(), - KeyName :: zx:key_id(), - Key :: term(). -%% @private -%% Loads the appropriate User, KeyID and reads in a registered key for use in -%% connect_auth/4. - -prep_auth(Realm) -> - UserName = zx_local:select_user(Realm), - KeyName = zx_local:select_private_key({Realm, UserName}), - {ok, Key} = zx_key:load(private, {Realm, KeyName}), - {UserName, KeyName, Key}. - - -pack_and_sign(Command, Payload, Key) -> - Bin = term_to_binary(Payload), - Signed = <>, - Sig = public_key:sign(Signed, sha512, Key), - SSize = byte_size(Sig), - <>. - - -done(Socket) -> - ok = inet:setopts(Socket, [{active, once}]), - receive - {tcp, Socket, <<0:1, 0:7>>} -> - zx_net:disconnect(Socket); - {tcp, Socket, Bin} -> - ok = zx_net:disconnect(Socket), - {error, zx_net:err_in(Bin)}; - {tcp_closed, Socket} -> - {error, tcp_closed} - after 5000 -> - done(Socket, timeout) - end. - - -done(Socket, Reason) -> - ok = zx_net:disconnect(Socket), - case is_binary(Reason) of - true -> {error, zx_net:err_in(Reason)}; - false -> {error, Reason} - end. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_sys_conf.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_sys_conf.erl deleted file mode 100644 index f92929e..0000000 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_sys_conf.erl +++ /dev/null @@ -1,329 +0,0 @@ -%%% @doc -%%% zx_sys_conf: An interface to etc/sys.conf -%%% -%%% It may seem overkill to write an interface module for a config file that only tracks -%%% five things, but scattering this all around the project is just a bit too l33t for -%%% an infrastructure project like ZX. -%%% -%%% Each exported function that is named after an attribute has two versions, one of -%%% arity-1 and one of arity-2. The arity-1 version is a "getter", and the arity-2 -%%% version is a "setter". Other functions deal with the data in a way that returns -%%% an answer and updates the state accordingly. -%%% -%%% Bad configuration data causes a reset to defaults so that the system can function. -%%% -%%% TODO: Change this to a gen_server that just babysits the config data. -%%% @end - --module(zx_sys_conf). --author("Craig Everett "). --copyright("Craig Everett "). --license("GPL-3.0"). - --export([load/0, save/1, - timeout/1, timeout/2, - retries/1, retries/2, retry/1, retries_left/1, - maxconn/1, maxconn/2, - managed/1, managed/2, add_managed/2, rem_managed/2, - mirrors/1, mirrors/2, add_mirror/2, rem_mirror/2, - reset/0]). - --export_type([data/0]). - --include("zx_logger.hrl"). - - - -%%% Type Definitions - --record(d, - {timeout = 5 :: pos_integer(), - retries = 3 :: non_neg_integer(), - maxconn = 5 :: pos_integer(), - managed = sets:new() :: sets:set(zx:realm()), - mirrors = sets:new() :: sets:set(zx:host())}). - --opaque data() :: #d{}. - - - -%%% Interface functions - --spec load() -> data(). -%% @doc -%% Read from etc/sys.conf and return a populated data() record if it exists, or -%% populate default values and write a new one if it does not. If a damaged sys.conf -%% is discovered it will be repaired. This function is side-effecty so should only -%% be called by zx_daemon and utility code. - -load() -> - Path = path(), - case file:consult(Path) of - {ok, List} -> - populate_data(List); - {error, Reason} -> - ok = log(error, "Load ~160tp failed with: ~160tp", [Path, Reason]), - Data = #d{}, - ok = save(Data), - Data - end. - - -populate_data(List) -> - Timeout = - case proplists:get_value(timeout, List, 5) of - TO when is_integer(TO) and TO > 0 -> TO; - _ -> 5 - end, - Retries = - case proplists:get_value(retries, List, 3) of - RT when is_integer(RT) and RT > 0 -> RT; - _ -> 3 - end, - MaxConn = - case proplists:get_value(maxconn, List, 5) of - MC when is_integer(MC) and MC > 0 -> MC; - _ -> 5 - end, - Managed = - case proplists:get_value(managed, List, []) of - MN when is_list(MN) -> sets:from_list(MN); - _ -> sets:new() - end, - Mirrors = - case proplists:get_value(mirrors, List, []) of - MR when is_list(MR) -> sets:from_list(MR); - _ -> sets:new() - end, - #d{timeout = Timeout, - retries = Retries, - maxconn = MaxConn, - managed = Managed, - mirrors = Mirrors}. - - --spec save(data()) -> ok. -%% @doc -%% Save the current etc/sys.conf to disk. - -save(#d{timeout = Timeout, - retries = Retries, - maxconn = MaxConn, - managed = Managed, - mirrors = Mirrors}) -> - Terms = - [{timeout, Timeout}, - {retries, Retries}, - {maxconn, MaxConn}, - {managed, sets:to_list(Managed)}, - {mirrors, sets:to_list(Mirrors)}], - ok = zx_lib:write_terms(path(), Terms), - log(info, "Wrote etc/sys.conf"). - - --spec timeout(data()) -> pos_integer(). -%% @doc -%% Return the timeout value. - -timeout(#d{timeout = Timeout}) -> - Timeout. - - --spec timeout(Value, Data) -> NewData - when Value :: pos_integer(), - Data :: data(), - NewData :: data(). -%% @doc -%% Set the timeout attribute to a new value. - -timeout(Value, Data) when Value > 0 -> - Data#d{timeout = Value}. - - --spec retries(data()) -> non_neg_integer(). -%% @doc -%% Return the retries value. - -retries(#d{retries = Retries}) -> - Retries. - - --spec retries(Value, Data) -> NewData - when Value :: non_neg_integer(), - Data :: data(), - NewData :: data(). -%% @doc -%% Set the retries attribute to a new value. - -retries(Value, Data) when Value > 0 -> - Data#d{retries = Value}. - - --spec retry(Data) -> Result - when Data :: data(), - Result :: {ok, NewData} - | no_retries, - NewData :: data(). -%% @doc -%% Tell the caller whether there are any more retries remaining or return `ok' and -%% update the state. - -retry(#d{retries = {0, _}}) -> - no_retries; -retry(Data = #d{retries = {Remaining, Setting}}) -> - NewRemaining = Remaining - 1, - NewData = Data#d{retries = {NewRemaining, Setting}}, - {ok, NewData}. - - --spec retries_left(data()) -> non_neg_integer(). -%% @doc -%% Return the number of retries remaining. - -retries_left(#d{retries = {Remaining, _}}) -> - Remaining. - - --spec maxconn(data()) -> pos_integer(). -%% @doc -%% Return the value of maxconn. - -maxconn(#d{maxconn = MaxConn}) -> - MaxConn. - - --spec maxconn(Value, Data) -> NewData - when Value :: pos_integer(), - Data :: data(), - NewData :: data(). -%% @doc -%% Set the value of maxconn. - -maxconn(Value, Data) when is_integer(Value) and Value > 0 -> - Data#d{maxconn = Value}. - - --spec managed(data()) -> [zx:realm()]. -%% @doc -%% Return the list of realms managed by the current node. - -managed(#d{managed = Managed}) -> - sets:to_list(Managed). - - --spec managed(List, Data) -> NewData - when List :: [zx:realm()], - Data :: data(), - NewData :: data(). -%% @doc -%% Reset the set of managed realms entirely. -%% The realms must be configured on the current realm at a minimum. - -managed(List, Data) -> - Desired = sets:from_list(List), - Configured = sets:from_list(zx_lib:list_realms()), - NewManaged = sets:intersection(Desired, Configured), - Data#d{managed = NewManaged}. - - --spec add_managed(Realm, Data) -> Result - when Realm :: zx:realm(), - Data :: data(), - Result :: {ok, NewData} - | {error, unconfigured}, - NewData :: data(). -%% @doc -%% Add a new realm to the list of managed realms. The new realm must be configured on -%% the current node. This node will then behave as the prime node for the realm (whether -%% it is or not). - -add_managed(Realm, Data = #d{managed = Managed}) -> - case zx_lib:realm_exists(Realm) of - true -> - NewData = Data#d{managed = sets:add_element(Realm, Managed)}, - ok = log(info, "Now managing realm: ~160tp", [Realm]), - {ok, NewData}; - false -> - ok = log(warning, "Cannot manage unconfigured realm: ~160tp", [Realm]), - {error, unconfigured} - end. - - --spec rem_managed(Realm, Data) -> Result - when Realm :: zx:realm(), - Data :: data(), - Result :: {ok, NewData} - | {error, unmanaged}, - NewData :: data(). -%% @doc -%% Stop managing a realm. - -rem_managed(Realm, Data = #d{managed = Managed}) -> - case sets:is_element(Realm, Managed) of - true -> - NewData = Data#d{managed = sets:del_element(Realm, Managed)}, - ok = log(info, "No longer managing realm: ~160tp", [Realm]), - {ok, NewData}; - false -> - ok = log(warning, "Cannot stop managing unmanaged realm: ~160tp", [Realm]), - {error, unmanaged} - end. - - --spec mirrors(data()) -> [zx:host()]. -%% @doc -%% Return the list of private mirrors. - -mirrors(#d{mirrors = Mirrors}) -> - sets:to_list(Mirrors). - - --spec mirrors(Hosts, Data) -> NewData - when Hosts :: [zx:host()], - Data :: data(), - NewData :: data(). -%% @private -%% Reset the mirror configuration. - -mirrors(Hosts, Data) -> - Data#d{mirrors = sets:from_list(Hosts)}. - - --spec add_mirror(Host, Data) -> NewData - when Host :: zx:host(), - Data :: data(), - NewData :: data(). -%% @doc -%% Add a mirror to the permanent configuration. - -add_mirror(Host, Data = #d{mirrors = Mirrors}) -> - Data#d{mirrors = sets:add_element(Host, Mirrors)}. - - --spec rem_mirror(Host, Data) -> NewData - when Host :: zx:host(), - Data :: data(), - NewData :: data(). -%% @private -%% Remove a host from the list of permanent mirrors. - -rem_mirror(Host, Data = #d{mirrors = Mirrors}) -> - Data#d{mirrors = sets:del_element(Host, Mirrors)}. - - --spec reset() -> data(). -%% @private -%% Reset sys.conf. - -reset() -> - Data = #d{}, - save(Data), - Data. - - --spec path() -> file:filename(). -%% @private -%% Return the path to $ZOMP_DIR/etc/sys.conf. - -path() -> - filename:join(zx_lib:path(etc), "sys.conf"). diff --git a/zomp/lib/otpr/zx/0.1.0/zomp.meta b/zomp/lib/otpr/zx/0.1.0/zomp.meta deleted file mode 100644 index 8188835..0000000 --- a/zomp/lib/otpr/zx/0.1.0/zomp.meta +++ /dev/null @@ -1,4 +0,0 @@ -{deps,[]}. -{package_id,{"otpr","zx",{0,1,0}}}. -{prefix,"zx_"}. -{type,app}. diff --git a/zomp/lib/otpr/zx/0.1.0/Emakefile b/zomp/lib/otpr/zx/0.2.0/Emakefile similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/Emakefile rename to zomp/lib/otpr/zx/0.2.0/Emakefile diff --git a/zomp/lib/otpr/zx/0.1.0/LICENSE b/zomp/lib/otpr/zx/0.2.0/LICENSE similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/LICENSE rename to zomp/lib/otpr/zx/0.2.0/LICENSE diff --git a/zomp/lib/otpr/zx/0.1.0/ebin/zx.app b/zomp/lib/otpr/zx/0.2.0/ebin/zx.app similarity index 55% rename from zomp/lib/otpr/zx/0.1.0/ebin/zx.app rename to zomp/lib/otpr/zx/0.2.0/ebin/zx.app index 9f0b007..f8d96c5 100644 --- a/zomp/lib/otpr/zx/0.1.0/ebin/zx.app +++ b/zomp/lib/otpr/zx/0.2.0/ebin/zx.app @@ -1,7 +1,8 @@ {application,zx, [{description,"Zomp client program"}, - {vsn,"0.1.0"}, + {vsn,"0.2.0"}, {applications,[stdlib,kernel]}, {modules,[zx,zx_auth,zx_conn,zx_conn_sup,zx_daemon,zx_key, - zx_lib,zx_local,zx_net,zx_sup,zx_sys_conf,zx_tty]}, + zx_lib,zx_local,zx_net,zx_peer,zx_peer_man, + zx_peer_sup,zx_peers,zx_proxy,zx_sup,zx_tty,zx_zsp]}, {mod,{zx,none}}]}. diff --git a/zomp/lib/otpr/zx/0.2.0/include/zx_logger.hrl b/zomp/lib/otpr/zx/0.2.0/include/zx_logger.hrl new file mode 100644 index 0000000..0059aa4 --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/include/zx_logger.hrl @@ -0,0 +1,63 @@ +-export([log/2, tell/1, tell/2]). + + +-spec log(Level, Format) -> ok + when Level :: info + | warning + | error, + Format :: string(). +%% @private +%% @equiv log(Level, Format, []) + +log(Level, Format) -> + log(Level, Format, []). + + +-spec log(Level, Format, Args) -> ok + when Level :: logger:level(), + Format :: string(), + Args :: [term()]. +%% @private +%% A logging abstraction to hide whatever logging back end is actually in use. +%% Format must adhere to Erlang format string rules, and the arity of Args must match +%% the provided format. + +log(Level, Format, Args) -> + Raw = io_lib:format("~w ~w: " ++ Format, [?MODULE, self() | Args]), + Entry = unicode:characters_to_list(Raw), + logger:log(Level, Entry). + + +-spec tell(Message) -> ok + when Message :: string(). + +tell(Message) -> + tell(Message, []). + + +tell(Format, Args) when is_list(Format) -> + tell(info, Format, Args); +tell(Level, Message) when is_atom(Level) -> + tell(Level, Message, []). + + +-spec tell(Level, Format, Args) -> ok + when Level :: logger:level(), + Format :: string(), + Args :: [term()]. + +tell(Level, Format, Args) -> + ok = log(Level, Format, Args), + Out = io_lib:format(Format, Args), + Message = unicode:characters_to_list([Out, "~n"]), + io:format(Message). + + +%-spec show(Parent, Format, Args) -> ok +% when Parent :: wx:frame(), +% Format :: string(), +% Args :: [term()]. +% +% TODO: Convenience log + info/error modal for WX +% +%show(Parent, Format, Args) -> diff --git a/zomp/lib/otpr/zx/0.1.0/make_zx b/zomp/lib/otpr/zx/0.2.0/make_zx similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/make_zx rename to zomp/lib/otpr/zx/0.2.0/make_zx diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx.erl b/zomp/lib/otpr/zx/0.2.0/src/zx.erl similarity index 65% rename from zomp/lib/otpr/zx/0.1.0/src/zx.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx.erl index 93cb123..982576d 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx.erl @@ -24,24 +24,25 @@ %%% @end -module(zx). +-vsn("0.2.0"). -behavior(application). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). --export([do/0, do/1]). +-export([do/0]). -export([subscribe/1, unsubscribe/0]). +-export([list/0, list/1, list/2, list/3, latest/1]). -export([start/2, stop/1, stop/0]). --export([usage_exit/1]). -export_type([serial/0, package_id/0, package/0, realm/0, name/0, version/0, identifier/0, host/0, - key_id/0, key_name/0, + key_data/0, key_bin/0, key_id/0, key_name/0, user_id/0, user_name/0, contact_info/0, user_data/0, lower0_9/0, label/0, - package_meta/0, ss_tag/0, + package_meta/0, ss_tag/0, search_tag/0, outcome/0]). -include("zx_logger.hrl"). @@ -60,10 +61,13 @@ Patch :: non_neg_integer() | z}. -type host() :: {string() | inet:ip_address(), inet:port_number()}. -type key_data() :: {Name :: key_name(), - Public :: none | {SHA512 :: binary(), DER :: binary()}, - Private :: none | {SHA512 :: binary(), DER :: binary()}}. + Public :: none | key_bin(), + Private :: none | key_bin()}. +-type key_bin() :: {Sig :: none | {key_name(), binary()}, + Der :: binary()}. -type key_id() :: {realm(), key_name()}. --type key_name() :: lower0_9(). +-type key_name() :: key_hash(). +-type key_hash() :: binary(). -type user_data() :: {ID :: user_id(), RealName :: string(), Contact :: [contact_info()], @@ -73,10 +77,12 @@ -type contact_info() :: {Type :: string(), Data :: string()}. -type lower0_9() :: [$a..$z | $0..$9 | $_]. -type label() :: [$a..$z | $0..$9 | $_ | $- | $.]. --type package_meta() :: #{package_id := package_id(), - deps := [package_id()], - type := app | lib}. +-type package_meta() :: #{package_id := package_id(), + search_tags := [search_tag()], + deps := [package_id()], + type := app | lib}. -type ss_tag() :: {serial(), calendar:timestamp()}. +-type search_tag() :: string(). -type outcome() :: ok | {error, Reason :: term()} @@ -91,7 +97,9 @@ do() -> ok = io:setopts([{encoding, unicode}]), - do([]). + ok = start(), + Args = init:get_plain_arguments(), + do(Args). -spec do(Args) -> no_return() @@ -107,50 +115,62 @@ do(["help", "dev"]) -> do(["help", "sysop"]) -> done(help(sysop)); do(["run", PackageString | ArgV]) -> - ok = start(), + ok = zx_daemon:connect(), not_done(run(PackageString, ArgV)); do(["list", "realms"]) -> done(zx_local:list_realms()); do(["list", "packages", Realm]) -> - ok = start(), + ok = zx_daemon:connect(), done(zx_local:list_packages(Realm)); do(["list", "versions", PackageName]) -> - ok = start(), + ok = zx_daemon:connect(), done(zx_local:list_versions(PackageName)); do(["latest", PackageString]) -> - ok = start(), + ok = zx_daemon:connect(), done(zx_local:latest(PackageString)); do(["import", "realm", RealmFile]) -> done(zx_local:import_realm(RealmFile)); do(["drop", "realm", Realm]) -> done(zx_local:drop_realm(Realm)); -do(["logpath", Package, Run]) -> - done(zx_local:logpath(Package, Run)); -do(["status"]) -> - done(zx_local:status()); +do(["logpath", PackageString, AgoString]) -> + case try list_to_integer(AgoString) catch Error:Reason -> {Error, Reason} end of + {error, badarg} -> done(help(user)); + Ago -> done(zx_local:logpath(PackageString, Ago)) + end; 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(["upgrade"]) -> + ok = zx_daemon:connect(), + done(upgrade()); do(["create", "project"]) -> + ok = zx_daemon:connect(), done(zx_local:create_project()); do(["runlocal" | ArgV]) -> - ok = start(), + ok = zx_daemon:connect(), not_done(run_local(ArgV)); +do(["rundir", Path | ArgV]) -> + ok = zx_daemon:connect(), + not_done(run_dir(Path, ArgV)); do(["init"]) -> + ok = zx_daemon:connect(), ok = compatibility_check([unix]), done(zx_local:initialize()); do(["list", "deps"]) -> done(zx_local:list_deps()); do(["list", "deps", PackageString]) -> + ok = zx_daemon:connect(), done(zx_local:list_deps(PackageString)); do(["set", "dep", PackageString]) -> done(zx_local:set_dep(PackageString)); do(["drop", "dep", PackageString]) -> - PackageID = zx_lib:package_id(PackageString), - done(zx_local:drop_dep(PackageID)); + done(zx_local:drop_dep(PackageString)); +do(["provides", Module]) -> + ok = zx_daemon:connect(), + done(zx_local:provides(Module)); do(["verup", Level]) -> ok = compatibility_check([unix]), done(zx_local:verup(Level)); @@ -159,10 +179,6 @@ do(["set", "version", VersionString]) -> done(zx_local:set_version(VersionString)); do(["update", ".app"]) -> done(zx_local:update_app_file()); -do(["create", "plt"]) -> - done(zx_local:create_plt()); -do(["dialyze"]) -> - done(zx_local:dialyze()); do(["package"]) -> {ok, TargetDir} = file:get_cwd(), done(zx_local:package(TargetDir)); @@ -183,8 +199,9 @@ 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(["sync", "keys"]) -> + ok = zx_daemon:connect(), + done(zx_auth:sync_keys()); do(["create", "user"]) -> done(zx_local:create_user()); do(["create", "userfile"]) -> @@ -195,32 +212,30 @@ do(["export", "user"]) -> done(zx_local:export_user()); do(["import", "user", ZdufFile]) -> done(zx_local:import_user(ZdufFile)); +do(["list", "users", Realm]) -> + done(zx_auth:list_users(Realm)); do(["list", "packagers", PackageName]) -> done(zx_auth:list_packagers(PackageName)); do(["list", "maintainers", PackageName]) -> done(zx_auth:list_maintainers(PackageName)); do(["list", "sysops", Realm]) -> - done(zx_auth:list_sysops(Realm)); + ok = zx_daemon:connect(), + done(zx_local:list_sysops(Realm)); do(["create", "realmfile"]) -> done(zx_local:create_realmfile()); do(["install", PackageFile]) -> case filelib:is_regular(PackageFile) of - true -> - ok = start(), - done(zx_daemon:install(PackageFile)); - false -> - done({error, "Target directory does not exist", 22}) + true -> done(zx_daemon:install(PackageFile)); + false -> done({error, ".zsp file 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(["rem", "user", Realm, UserName]) -> + done(zx_auth:rem_user(Realm, UserName)); do(["add", "packager", Package, UserName]) -> done(zx_auth:add_packager(Package, UserName)); do(["rem", "packager", Package, UserName]) -> @@ -229,31 +244,37 @@ 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(["add", "sysop", Realm, UserName]) -> + done(zx_auth:add_sysop(Realm, UserName)); do(["create", "realm"]) -> done(zx_local:create_realm()); do(["takeover", Realm]) -> - done(zx_local:takeover(Realm)); + done(zx_daemon:takeover(Realm)); do(["abdicate", Realm]) -> - done(zx_local:abdicate(Realm)); + done(zx_daemon:abdicate(Realm)); do(_) -> - usage_exit(22). + done(help(top)). -spec done(outcome()) -> no_return(). done(ok) -> - halt(0); + ok = zx_daemon:idle(), + init:stop(0); done({error, Code}) when is_integer(Code) -> - ok = log(error, "Operation failed with code: ~w", [Code]), - halt(Code); + ok = zx_daemon:idle(), + Message = "Operation failed with code: ~w", + ok = tell(error, Message, [Code]), + init:stop(Code); done({error, Reason}) -> - ok = log(error, "Operation failed with: ~160tp", [Reason]), - halt(1); + ok = zx_daemon:idle(), + Message = "Operation failed with: ~160tp", + ok = tell(error, Message, [Reason]), + init:stop(1); done({error, Info, Code}) -> - ok = log(error, Info), - halt(Code). + ok = zx_daemon:idle(), + ok = tell(error, "Error: ~160tp: ~160tp", [Info, Code]), + init:stop(Code). -spec not_done(outcome()) -> ok | no_return(). @@ -279,8 +300,8 @@ compatibility_check(Platforms) -> ok; false -> Message = "Unfortunately this command is not available on ~tw ~tw", - ok = log(error, Message, [Family, Name]), - halt(0) + ok = tell(error, Message, [Family, Name]), + init:stop() end. @@ -298,9 +319,46 @@ compatibility_check(Platforms) -> %% @equiv application:ensure_started(zx). start() -> -% ok = application:ensure_started(sasl), - ok = application:ensure_started(zx), - zx_daemon:init_connections(). + LogPath = + case init:get_plain_arguments() of + ["run", PackageString | _] -> + case zx_lib:package_id(PackageString) of + {ok, PackageID} -> zx_lib:new_logpath(PackageID); + Error -> done(Error) + end; + _ -> + {ok, Version} = zx_lib:string_to_version(os:getenv("ZX_VERSION")), + zx_lib:new_logpath({"otpr", "zx", Version}) + end, + ok = logger:remove_handler(default), + LoggerConf = + #{config => + #{burst_limit_enable => true, + burst_limit_max_count => 500, + burst_limit_window_time => 1000, + drop_mode_qlen => 200, + filesync_repeat_interval => no_repeat, + flush_qlen => 1000, + overload_kill_enable => false, + overload_kill_mem_size => 3000000, + overload_kill_qlen => 20000, + overload_kill_restart_after => 5000, + sync_mode_qlen => 10, + type => {file, LogPath}}, + filter_default => + stop, + filters => + [{remote_gl, {fun logger_filters:remote_gl/2, stop}}, + {domain, {fun logger_filters:domain/2, {log, super, [otp, sasl]}}}, + {no_domain, {fun logger_filters:domain/2, {log, undefined,[]}}}], + formatter => + {logger_formatter, #{legacy_header => false, single_line => true}}, + id => default, + level => all, + module => logger_std_h}, + ok = logger:add_handler(default, logger_std_h, LoggerConf), + ok = logger:set_primary_config(level, debug), + application:ensure_started(zx). -spec stop() -> ok | {error, Reason :: term()}. @@ -309,10 +367,16 @@ start() -> %% `ok' in the case that zx is already stopped. stop() -> + ok = tell("Shutting down runtime."), + ok = zx_daemon:idle(), case application:stop(zx) of - ok -> ok; - {error, {not_started, zx}} -> ok; - Error -> Error + ok -> + init:stop(); + {error, {not_started, zx}} -> + init:stop(); + Error -> + ok = tell(error, "zx:stop/0 failed with ~tp", [Error]), + init:stop(1) end. @@ -369,9 +433,75 @@ unsubscribe() -> +%%% Query Functions + +-spec list() -> Result + when Result :: {ok, [realm()]} + | {error, no_realms}. + +list() -> + case zx_lib:list_realms() of + [] -> {error, no_realms}; + Realms -> {ok, Realms} + end. + + +-spec list(realm()) -> Result + when Result :: {ok, [realm()]} + | {error, Reason}, + Reason :: bad_realm + | timeout + | network. + +list(Realm) -> + {ok, ID} = zx_daemon:list(Realm), + wait_result(ID). + + +-spec list(realm(), name()) -> Result + when Result :: {ok, [version()]} + | {error, Reason}, + Reason :: bad_realm + | bad_package + | timeout + | network. + +list(Realm, Name) -> + list(Realm, Name, {z, z, z}). + + +-spec list(realm(), name(), version()) -> Result + when Result :: {ok, [version()]} + | {error, Reason}, + Reason :: bad_realm + | bad_package + | bad_version + | timeout + | network. + +list(Realm, Name, Version) -> + {ok, ID} = zx_daemon:list(Realm, Name, Version), + wait_result(ID). + + +-spec latest(package_id()) -> Result + when Result :: {ok, package_id()} + | {error, Reason}, + Reason :: bad_realm + | bad_package + | bad_version + | timeout + | network. + +latest(PackageID) -> + {ok, ID} = zx_daemon:latest(PackageID), + wait_result(ID). + + + %%% Execution of application --spec run(PackageString, RunArgs) -> no_return() +-spec run(PackageString, RunArgs) -> zx:outcome() when PackageString :: string(), RunArgs :: [string()]. %% @private @@ -393,32 +523,43 @@ unsubscribe() -> run(PackageString, RunArgs) -> case zx_lib:package_id(PackageString) of - {ok, FuzzyID} -> run2(FuzzyID, RunArgs); - Error -> log(info, "run/2 got ~tp", [Error]), Error + {ok, {"otpr", "zomp", Version}} -> run2_maybe(Version, RunArgs); + {ok, FuzzyID} -> run2(FuzzyID, RunArgs); + Error -> Error end. -run2(FuzzyID = {Realm, Name, _}, RunArgs) -> - case resolve_installed_version(FuzzyID) of - exact -> run3(FuzzyID, RunArgs); - {ok, Installed} -> run3({Realm, Name, Installed}, RunArgs); - not_found -> run3_maybe(FuzzyID, RunArgs) +run2_maybe(Version, RunArgs) -> + {ok, Managed} = zx_daemon:conf(managed), + case lists:member("otpr", Managed) of + true -> run_zomp(RunArgs); + false -> run2({"otpr", "zomp", Version}, RunArgs) + end. + +run_zomp(RunArgs) -> + {ok, Dirs} = file:list_dir(zx_lib:path(lib, "otpr", "zomp")), + Versions = lists:foldl(fun tuplize/2, [], Dirs), + case zx_lib:find_latest_compatible({z, z, z}, Versions) of + not_found -> {error, not_found}; + {ok, Latest} -> run3({"otpr", "zomp", Latest}, RunArgs) + end. + +tuplize(String, Acc) -> + case zx_lib:string_to_version(String) of + {ok, Version} -> [Version | Acc]; + _ -> Acc + end. + + +run2(FuzzyID, RunArgs) -> + case resolve_version(FuzzyID) of + {installed, PackageID} -> run3(PackageID, RunArgs); + {fetch, PackageID} -> run3_maybe(PackageID, RunArgs); + Error -> Error 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 @@ -434,8 +575,33 @@ run3(PackageID, RunArgs) -> execute(Type, PackageID, Meta, Dir, RunArgs). --spec run_local(RunArgs) -> no_return() - when RunArgs :: [term()]. +-spec resolve_version(PackageID) -> Result + when PackageID :: package_id(), + Result :: not_found + | exact + | {ok, Installed :: version()}. +%% @private +%% Resolve the provided PackageID to the latest matching installed package directory +%% version if one exists, returning a value that indicates whether an exact match was +%% found (in the case of a full version input), a version matching a partial version +%% input was found, or no match was found at all. + +resolve_version(PackageID = {_, _, {X, Y, Z}}) + when is_integer(X), is_integer(Y), is_integer(Z) -> + case zx_lib:installed(PackageID) of + true -> {installed, PackageID}; + false -> {fetch, PackageID} + end; +resolve_version(PackageID = {Realm, Name, _}) -> + {ok, ID} = zx_daemon:latest(PackageID), + case wait_result(ID) of + {ok, Latest} -> resolve_version({Realm, Name, Latest}); + Error -> Error + end. + + +-spec run_local(RunArgs) -> zx:outcome() | no_return() + when RunArgs :: [string()]. %% @private %% Execute a local project from source from the current directory, satisfying dependency %% requirements via the locally installed zomp lib cache. The project must be @@ -446,6 +612,30 @@ run3(PackageID, RunArgs) -> %% and use zx commands to add or drop dependencies made available via zomp. run_local(RunArgs) -> + {ok, ProjectDir} = file:get_cwd(), + run_project(ProjectDir, ProjectDir, RunArgs). + + +-spec run_dir(TargetDir, RunArgs) -> zx:outcome() | no_return() + when TargetDir :: file:filename(), + RunArgs :: [string()]. + +run_dir(TargetDir, RunArgs) -> + {ok, ExecDir} = file:get_cwd(), + case file:set_cwd(TargetDir) of + ok -> + {ok, ProjectDir} = file:get_cwd(), + run_project(ProjectDir, ExecDir, RunArgs); + Error -> Error + end. + + +-spec run_project(ProjectDir, ExecDir, RunArgs) -> zx:outcome() | no_return() + when ProjectDir :: file:filename(), + ExecDir :: file:filename(), + RunArgs :: [string()]. + +run_project(ProjectDir, ExecDir, RunArgs) -> {ok, Meta} = zx_lib:read_project_meta(), PackageID = {_, Name, _} = maps:get(package_id, Meta), Type = maps:get(type, Meta), @@ -454,15 +644,14 @@ run_local(RunArgs) -> true = os:putenv(Name ++ "_include", filename:join(Dir, "include")), case prepare(Deps) of ok -> - ok = file:set_cwd(Dir), + ok = file:set_cwd(ProjectDir), ok = zx_lib:build(), - ok = file:set_cwd(zx_lib:zomp_dir()), + ok = file:set_cwd(ExecDir), execute(Type, PackageID, Meta, Dir, RunArgs); Error -> Error end. - -spec prepare([zx:package_id()]) -> ok. %% @private %% Execution prep common to all packages. @@ -488,15 +677,45 @@ make([Dep | Rest]) -> Error -> Error end; make([]) -> - log(info, "Deps prepared."). + tell("Builds complete."). - include_env(PackageID = {_, Name, _}) -> Path = filename:join(zx_lib:ppath(lib, PackageID), "include"), os:putenv(Name ++ "_include", Path). +-spec upgrade() -> zx:outcome(). +%% @private +%% Upgrade ZX itself to the latest version.. + +upgrade() -> + ZxDir = os:getenv("ZX_DIR"), + {ok, Meta} = zx_lib:read_project_meta(ZxDir), + PackageID = {Realm, Name, Current} = maps:get(package_id, Meta), + {ok, PackageString} = zx_lib:package_string(PackageID), + ok = tell("Current version: ~s~n", [PackageString]), + {ok, ID} = zx_daemon:latest({Realm, Name}), + case wait_result(ID) of + {ok, Current} -> + tell("Running latest version.~n"); + {ok, Latest} when Latest > Current -> + NewID = {Realm, Name, Latest}, + ok = acquire([NewID], [NewID]), + {ok, LatestString} = zx_lib:version_to_string(Latest), + VersionTxt = filename:join(zx_lib:path(etc), "version.txt"), + ok = file:write_file(VersionTxt, LatestString), + {ok, NewString} = zx_lib:package_string(NewID), + tell("Upgraded to ~s~n.", [NewString]); + {ok, Available} when Available < Current -> + {ok, AvailableString} = zx_lib:version_to_string(Available), + Message = "Local version is newer than ~s. Nothing to do.~n", + ok = tell(Message, [AvailableString]); + Error -> + Error + end. + + -spec fetch(zx:package_id()) -> zx:outcome(). fetch(PackageID) -> @@ -508,12 +727,12 @@ fetch2(ID) -> {result, ID, done} -> ok; {result, ID, {hops, Count}} -> - ok = log(info, "Inbound; ~w hops away.", [Count]), + ok = tell("Inbound; ~w hops away.", [Count]), fetch2(ID); - {result, ID, {error, Reason}} -> - {error, Reason, 1} + {result, ID, Error} -> + Error after 10000 -> - {error, timeout, 62} + {error, timeout} end. @@ -529,17 +748,17 @@ fetch2(ID) -> execute(app, PackageID, Meta, Dir, RunArgs) -> {ok, PackageString} = zx_lib:package_string(PackageID), - ok = log(info, "Starting ~ts.", [PackageString]), + ok = tell("Starting ~ts.", [PackageString]), Name = element(2, PackageID), ok = zx_daemon:pass_meta(Meta, Dir, RunArgs), AppTag = list_to_atom(Name), ok = ensure_all_started(AppTag), - log(info, "Launcher complete."); + tell("Launcher complete."); execute(lib, PackageID, _, _, _) -> Message = "Lib ~ts is available on the system, but is not a standalone app.", {ok, PackageString} = zx_lib:package_string(PackageID), - ok = log(info, Message, [PackageString]), - halt(0). + ok = tell(Message, [PackageString]), + init:stop(). -spec ensure_all_started(AppMod) -> ok @@ -554,39 +773,7 @@ execute(lib, PackageID, _, _, _) -> ensure_all_started(AppMod) -> case application:ensure_all_started(AppMod) of {ok, []} -> ok; - {ok, Apps} -> log(info, "Started ~160tp", [Apps]) - end. - - --spec resolve_installed_version(PackageID) -> Result - when PackageID :: package_id(), - Result :: not_found - | exact - | {ok, Installed :: version()}. -%% @private -%% Resolve the provided PackageID to the latest matching installed package directory -%% version if one exists, returning a value that indicates whether an exact match was -%% found (in the case of a full version input), a version matching a partial version -%% input was found, or no match was found at all. - -resolve_installed_version({Realm, Name, Version}) -> - PackageDir = zx_lib:path(lib, Realm, Name), - case filelib:is_dir(PackageDir) of - true -> resolve_installed_version(PackageDir, Version); - false -> not_found - end. - - -resolve_installed_version(PackageDir, Version) -> - DirStrings = filelib:wildcard("*", PackageDir), - Versions = lists:foldl(fun tuplize/2, [], DirStrings), - zx_lib:find_latest_compatible(Version, Versions). - - -tuplize(String, Acc) -> - case zx_lib:string_to_version(String) of - {ok, Version} -> [Version | Acc]; - _ -> Acc + {ok, Apps} -> tell("Started ~160tp", [Apps]) end. @@ -600,17 +787,6 @@ wait_result(ID) -> %%% Usage --spec usage_exit(Code) -> no_return() - when Code :: integer(). -%% @private -%% A convenience function that will display the zx usage message before halting -%% with the provided exit code. - -usage_exit(Code) -> - ok = lists:foreach(fun io:format/1, usage_all()), - halt(Code). - - help(top) -> show_help(); help(user) -> show_help([usage_header(), usage_user(), usage_spec()]); help(dev) -> show_help([usage_header(), usage_dev(), usage_spec()]); @@ -628,15 +804,11 @@ show_help() -> show_help(Info) -> lists:foreach(fun io:format/1, Info). -usage_all() -> - [usage_header(), usage_user(), usage_dev(), usage_sysop(), usage_spec()]. - usage_header() -> "ZX usage: zx [command] [object] [args]~n~n". usage_user() -> "User Actions:~n" - " zx help~n" " zx run PackageID [Args]~n" " zx list realms~n" " zx list packages Realm~n" @@ -654,15 +826,15 @@ usage_dev() -> "Developer/Packager/Maintainer Actions:~n" " zx create project~n" " zx runlocal [Args]~n" + " zx rundir Path [Args]~n" " zx init~n" " zx list deps [PackageID]~n" " zx set dep PackageID~n" " zx drop dep PackageID~n" + " zx provides Module~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" @@ -675,9 +847,11 @@ usage_dev() -> " zx create keypair~n" " zx export user~n" " zx import user [ZPUF | ZDUF]~n" + " zx list users Realm~n" " zx list packagers PackageName~n" " zx list maintainers PackageName~n" " zx list sysops Realm~n" + " zx create realmfile~n" " zx install ZSP~n~n". usage_sysop() -> @@ -685,13 +859,11 @@ usage_sysop() -> " 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". @@ -703,6 +875,7 @@ usage_spec() -> " 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" + " Module :: Name of a code module.~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" diff --git a/zomp/lib/otpr/zx/0.2.0/src/zx_auth.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_auth.erl new file mode 100644 index 0000000..85bb475 --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_auth.erl @@ -0,0 +1,588 @@ +%%% @doc +%%% ZX Auth +%%% +%%% This module is where all the AUTH type command code lives. AUTH commands are special +%%% because they do not involve the zx_daemon at all, though they do perform network +%%% operations. +%%% +%%% All AUTH procedures terminate the runtime once complete. +%%% @end + +-module(zx_auth). +-vsn("0.2.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0"). + +-export([list_users/1, list_packagers/1, list_maintainers/1, + add_packager/2, rem_packager/2, + add_maintainer/2, rem_maintainer/2, + list_pending/1, list_approved/1, + submit/1, review/1, approve/1, reject/1, accept/1, + add_package/1, + keychain/1, list_user_keys/1, + add_user/1]). + +-include("zx_logger.hrl"). + + + +%%% Functions + +list_users(Realm) -> list_users(1, Realm). + +list_packagers(PackageString) -> list_users(2, PackageString). + +list_maintainers(PackageString) -> list_users(3, PackageString). + + +-spec list_users(Command, Target) -> zx:outcome() + when Command :: 1..3, + Target :: zx:realm() | zx:package(). + +list_users(Command, Target) -> + case uu_package_request(Command, Target) of + {ok, Users} -> lists:foreach(fun zx_lib:print_user/1, Users); + Error -> Error + end. + + +add_packager(Package, User) -> su_user_request(4, Package, User). + +rem_packager(Package, User) -> su_user_request(5, Package, User). + +add_maintainer(Package, User) -> su_user_request(6, Package, User). + +rem_maintainer(Package, User) -> su_user_request(7, Package, User). + + +-spec list_pending(PackageString :: string()) -> zx:outcome(). +%% @private +%% List the versions of a package that are pending review. The package name is input by +%% the user as a string of the form "otpr-zomp" and the output is a list of full +%% package IDs, printed one per line to stdout (like "otpr-zomp-3.2.2"). + +list_pending(PackageString) -> + Command = 8, + case uu_package_request(Command, PackageString) of + {ok, Versions} -> lists:foreach(fun print_version/1, Versions); + Error -> Error + end. + +print_version(Tuple) -> + {ok, VersionString} = zx_lib:version_to_string(Tuple), + io:format("~ts~n", [VersionString]). + + +-spec list_approved(zx:realm()) -> zx:outcome(). +%% @private +%% List the package ids of all packages waiting in the resign queue for the given realm, +%% printed to stdout one per line. + +list_approved(Realm) -> + {ok, Realms} = zx_daemon:list(), + case lists:member(Realm, Realms) of + true -> list_approved2(Realm); + false -> {error, bad_realm} + end. + +list_approved2(Realm) -> + Command = 9, + case make_uu_request(Command, Realm) of + {ok, PackageIDs} -> + Print = + fun({Name, Version}) -> + {ok, PackageString} = zx_lib:package_string({Realm, Name, Version}), + io:format("~ts~n", [PackageString]) + end, + lists:foreach(Print, PackageIDs); + Error -> + Error + end. + + +-spec submit(ZspPath :: file:filename()) -> zx:outcome(). +%% @private +%% Submit a package to the appropriate "prime" server for the given realm. + +submit(ZspPath) -> + case file:read_file(ZspPath) of + {ok, ZspBin} -> submit2(ZspBin); + Error -> Error + end. + +submit2(ZspBin) -> + case zx_zsp:verify(ZspBin) of + ok -> submit3(ZspBin); + Error -> Error + end. + +submit3(ZspBin) -> + {ok, {{Realm, Name, Version}, KeyName, _, _, _}} = zx_zsp:meta(ZspBin), + UserName = select_auth(Realm), + case zx_daemon:get_key(private, {Realm, KeyName}) of + {ok, DKey} -> + Payload = {Realm, UserName, KeyName, {Name, Version}}, + submit4(Payload, Realm, ZspBin, DKey); + Error -> + Message = + "The private half of the key used to sign this package is not present " + "on the local system.~n" + "Import it and try again or do `zx resign ZspPath`.~n", + ok = io:format(Message), + Error + end. + +submit4(Payload, Realm, ZspBin, DKey) -> + case connect(Realm) of + {ok, Socket} -> submit5(Socket, Payload, ZspBin, DKey); + Error -> Error + end. + +submit5(Socket, Payload, ZspBin, DKey) -> + Command = 10, + Request = pack_and_sign(Command, Payload, DKey), + ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>), + case zx_net:tx(Socket, ZspBin) of + ok -> done(Socket); + {error, Reason} -> done(Socket, Reason) + end. + + +-spec review(PackageString :: string()) -> zx:outcome(). + +review(PackageString) -> + case zx_lib:package_id(PackageString) of + {ok, PackageID} -> review2(PackageID); + Error -> Error + end. + +review2(PackageID = {Realm, _, _}) -> + case connect(Realm) of + {ok, Socket} -> review3(PackageID, Socket); + Error -> Error + end. + +review3(PackageID, Socket) -> + Command = 11, + TermBin = term_to_binary(PackageID), + Request = <<0:24, Command:8, TermBin/binary>>, + ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", Request/binary>>), + receive + {tcp, Socket, <<0:1, 0:7>>} -> review4(PackageID, Socket); + {tcp, Socket, Bin} -> done(Socket, Bin); + {tcp_closed, Socket} -> {error, tcp_closed} + after 5000 -> done(Socket, timeout) + end. + +review4(PackageID, Socket) -> + case zx_net:rx(Socket) of + {ok, ZspBin} -> + ok = zx_net:disconnect(Socket), + review5(PackageID, ZspBin); + {error, Reason} -> + done(Socket, Reason) + end. + +review5(PackageID, ZspBin) -> + {ok, Requested} = zx_lib:package_string(PackageID), + case zx_zsp:meta(ZspBin) of + {ok, {PackageID, _, _, _, _}} -> + zx_zsp:extract(ZspBin, cwd); + {ok, {UnexpectedID, _, _, _, _}} -> + {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 + end. + + +approve(Package) -> su_package_request(12, Package). + +reject(Package) -> su_package_request(13, Package). + + +-spec accept(ZspPath :: file:filename()) -> zx:outcome(). + +accept(ZspPath) -> + case file:read_file(ZspPath) of + {ok, ZspBin} -> accept2(ZspBin); + Error -> Error + end. + +accept2(ZspBin) -> + case zx_zsp:meta(ZspBin) of + {ok, Meta} -> accept3(ZspBin, Meta); + error -> {error, bad_package} + end. + +accept3(ZspBin, Meta) -> + Realm = element(1, element(1, Meta)), + case connect_auth(Realm) of + {ok, AuthConn} -> accept4(ZspBin, Meta, AuthConn); + Error -> Error + end. + +accept4(ZspBin, Meta = {_, KeyName, _, _, _}, AuthConn = {_, KeyName, Key, _, _}) -> + case zx_zsp:verify(ZspBin, Key) of + true -> + accept5(ZspBin, Meta, AuthConn); + false -> + Message = + "~nALERT!~n" + "This package is not signed with the key it claims!~n" + "Resign this package with `zx resign [ZspPath]` to fix the problem.~n", + ok = io:format(Message), + {error, bad_sig} + end; +accept4(_, {_, PackageKey, _, _, _}, {_, UserKey, _, _, _}) -> + Message = + "~nERROR: BAD KEY~n" + "The package signature key and your auth key do not match.~n" + "PackageKey: ~tp~n" + "UserKey : ~tp~n" + "Resign this package with `zx resign [ZspPath]` or select the same key~n", + ok = io:format(Message, [PackageKey, UserKey]), + {error, bad_key}. + +accept5(ZspBin, + {{Realm, Name, Version}, KeyName, Tags, Deps, Mods}, + {UserName, KeyName, Key, SSTag, Socket}) -> + Command = 14, + Payload = {Realm, {Name, Version}, Tags, Deps, Mods, byte_size(ZspBin)}, + Message = {SSTag, UserName, KeyName, Payload}, + Request = pack_and_sign(Command, Message, Key), + ok = gen_tcp:send(Socket, <<0:8, Request/binary>>), + ok = inet:setopts(Socket, [{active, once}]), + receive + {tcp, Socket, <<0:8>>} -> accept6(ZspBin, Socket); + {tcp, Socket, Bin} -> done(Socket, Bin); + {tcp_closed, Socket} -> {error, tcp_closed} + after 5000 -> done(Socket, timeout) + end. + +accept6(ZspBin, Socket) -> + ok = zx_net:tx(Socket, ZspBin), + done(Socket). + + +add_package(PackageString) -> + case zx_lib:package_id(PackageString) of + {ok, {R, N, {z, z, z}}} -> + {ok, Realms} = zx_daemon:list(), + case lists:member(R, Realms) of + true -> add_package2({R, N}); + false -> {error, bad_realm} + end; + {ok, _} -> + {error, "Must not include a version number.", 1}; + Error -> + Error + end. + +add_package2(Target) -> + Realm = element(1, Target), + case connect_auth(Realm) of + {ok, AuthConn} -> add_package3(Target, AuthConn); + Error -> Error + end. + +add_package3(Target, {UserName, KeyName, Key, Tag, Socket}) -> + Command = 15, + Payload = {Tag, UserName, KeyName, Target}, + Request = pack_and_sign(Command, Payload, Key), + ok = gen_tcp:send(Socket, <<0:8, Request/binary>>), + done(Socket). + + +-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}} -> add_user3(UserInfo, KeyData); + error -> {error, "Bad user file.", 1} + end. + +add_user3(UserInfo, KeyData) -> + Realm = proplists:get_value(realm, UserInfo), + case prep_auth(Realm) of + {ok, AuthData = {_, KeyName, Key}} -> + UserData = {proplists:get_value(username, UserInfo), + proplists:get_value(realname, UserInfo), + proplists:get_value(contact_info, UserInfo), + [sign_and_sterilize(KeyName, Key, KD) || KD <- KeyData]}, + add_user4(Realm, UserData, AuthData); + Error -> + Error + end. + +add_user4(Realm, UserData, AuthData) -> + case connect(Realm) of + {ok, Socket} -> make_su_request3(16, Realm, UserData, AuthData, Socket); + Error -> Error + end. + + +-spec list_user_keys(zx:user_id()) -> zx:outcome(). + +list_user_keys(UserID) -> make_uu_request(17, UserID). + + +keychain(KeyID) -> + Command = 19, + Realm = element(1, KeyID), + case make_uu_request(Command, KeyID) of + {ok, KeyData} -> keychain2(Realm, KeyData); + Error -> Error + end. + +keychain2(Realm, [{UserName, Key = {_, {{SigKeyName, Sig}, Bin}, none}} | Rest]) -> + {ok, SigKey} = zx_daemon:get_key(public, {Realm, SigKeyName}), + true = zx_key:verify(Bin, Sig, SigKey), + ok = zx_daemon:register_key({Realm, UserName}, Key), + keychain2(Realm, Rest); +keychain2(_, []) -> + ok. + + +%%% Generic Request Forms + +uu_package_request(Command, PackageString) -> + case zx_lib:package_id(PackageString) of + {ok, {Realm, Name, _}} -> make_uu_request(Command, {Realm, Name}); + Error -> Error + end. + + +-spec make_uu_request(Command, Target) -> zx:outcome() + when Command :: 1..3 | 8 | 9 | 11 | 17 | 22, + Target :: zx:realm() | zx:package() | zx:package_id() | zx:user_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), + done(Socket). + + +-spec su_user_request(Command, Package, User)-> zx:outcome() + when Command :: 4..7, + Package :: string(), + User :: zx:user_name(). + +su_user_request(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 su_package_request(Code :: 11 | 12, Package :: string()) -> zx:outcome(). + +su_package_request(Code, Package) -> + case zx_lib:package_id(Package) of + {ok, {Realm, Name, Version}} -> make_su_request(Code, Realm, {Name, Version}); + Error -> Error + end. + + +-spec make_su_request(Command, Realm, Data) -> zx:outcome() + when Command :: 4..7 | 10 | 12 | 13 | 16 | 18, + Realm :: zx:realm(), + Data :: term(). + +make_su_request(Command, Realm, Data) -> + case prep_auth(Realm) of + {ok, AuthData} -> make_su_request2(Command, Realm, Data, AuthData); + Error -> Error + end. + +make_su_request2(Command, Realm, Data, AuthData) -> + case connect(Realm) of + {ok, Socket} -> make_su_request3(Command, Realm, Data, AuthData, Socket); + Error -> Error + end. + +make_su_request3(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). + + + +%%% Utility Functions + +-spec prep_auth(Realm) -> {User, KeyName, Key} + when Realm :: zx:realm(), + User :: zx:user_id(), + KeyName :: zx:key_id(), + Key :: term(). +%% @private +%% Loads the appropriate User, KeyID and reads in a registered key for use in +%% connect_auth/4. + +prep_auth(Realm) -> + UserName = select_auth(Realm), + case zx_local:select_private_key({Realm, UserName}) of + {ok, {KeyName, Key}} -> + {ok, {UserName, KeyName, Key}}; + error -> + ok = log(error, "No private key exists for user ~tp.", [UserName]), + {error, no_key} + end. + + +pack_and_sign(Command, Payload, Key) -> + Bin = term_to_binary(Payload), + Signed = <>, + Sig = zx_key:sign(Signed, Key), + SSize = byte_size(Sig), + <>. + + +sign_and_sterilize(SigKeyName, SigKey, {Name, {_, Der}, _}) -> + Sig = zx_key:sign(Der, SigKey), + {Name, {{SigKeyName, Sig}, Der}, none}. + + + +%%% Connectiness with prime + +-spec select_auth(zx:realm()) -> zx:user_name(). + +select_auth(Realm) -> + Pattern = filename:join(zx_lib:path(etc, Realm), "*.user"), + LocalUsers = [filename:basename(UN, ".user") || UN <- filelib:wildcard(Pattern)], + case LocalUsers of + [] -> + Message = + "A user record is required to complete this action. Creating now...", + ok = log(info, Message), + zx_local:create_user(Realm); + [UserName] -> + UserName; + UserNames -> + Message = "Under what user's authority are you taking this action?~n", + ok = io:format(Message), + zx_tty:select_string(UserNames) + end. + + +-spec connect_auth(Realm) -> Result + when Realm :: zx:realm(), + Result :: {ok, AuthConn} + | {error, Reason}, + AuthConn :: {UserName :: zx:user_name(), + KeyName :: zx:key_name(), + Key :: term(), + SSTag :: zx:ss_tag(), + Socket :: gen_tcp:socket()}, + Reason :: term(). +%% @private +%% Connect to one of the servers in the realm constellation. + +connect_auth(Realm) -> + case zx_lib:load_realm_conf(Realm) of + {ok, RealmConf} -> + connect_auth2(Realm, RealmConf); + Error -> + ok = log(error, "Realm ~160tp is not configured.", [Realm]), + Error + end. + +connect_auth2(Realm, RealmConf) -> + case prep_auth(Realm) of + {ok, UserData} -> connect_auth3(Realm, RealmConf, UserData); + Error -> Error + end. + +connect_auth3(Realm, RealmConf, UserData) -> + {Host, Port} = maps:get(prime, RealmConf), + Options = [{packet, 4}, {mode, binary}, {active, once}], + case gen_tcp:connect(Host, Port, Options, 5000) of + {ok, Socket} -> + connect_auth4(Socket, Realm, UserData); + Error = {error, E} -> + ok = log(warning, "Connection problem: ~160tp", [E]), + {error, Error} + end. + +connect_auth4(Socket, Realm, UD = {UserName, KeyName, Key}) -> + Null = 0, + Timestamp = calendar:universal_time(), + Payload = {Realm, Timestamp, UserName, KeyName}, + NullRequest = pack_and_sign(Null, Payload, Key), + ok = gen_tcp:send(Socket, <<"ZOMP AUTH 1:", NullRequest/binary>>), + receive + {tcp, Socket, <<0:8, Bin/binary>>} -> connect_auth5(Socket, UD, Bin); + {tcp, Socket, Bin} -> done(Socket, Bin); + {tcp_closed, Socket} -> {error, tcp_closed} + after 5000 -> done(Socket, timeout) + end. + +connect_auth5(Socket, {UserName, KeyName, Key}, Bin) -> + case zx_lib:b_to_ts(Bin) of + {ok, Tag} -> {ok, {UserName, KeyName, Key, Tag, Socket}}; + error -> done(Socket, bad_response) + end. + + +connect(Realm) -> + case zx_lib:load_realm_conf(Realm) of + {ok, RealmConf} -> + {Host, Port} = maps:get(prime, RealmConf), + Options = [{packet, 4}, {mode, binary}, {nodelay, true}, {active, once}], + gen_tcp:connect(Host, Port, Options, 5000); + Error -> + ok = log(error, "Realm ~160tp is not configured.", [Realm]), + Error + end. + + +done(Socket) -> + ok = inet:setopts(Socket, [{active, once}]), + receive + {tcp, Socket, <<0:1, 0:7>>} -> + zx_net:disconnect(Socket); + {tcp, Socket, <<0:1, 0:7, Bin/binary>>} -> + ok = zx_net:disconnect(Socket), + case zx_lib:b_to_ts(Bin) of + error -> {error, bad_response}; + Term -> Term + end; + {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. + + +done(Socket, Reason) -> + ok = zx_net:disconnect(Socket), + case is_binary(Reason) of + true -> {error, zx_net:err_in(Reason)}; + false -> {error, Reason} + end. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_conn.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_conn.erl similarity index 79% rename from zomp/lib/otpr/zx/0.1.0/src/zx_conn.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_conn.erl index ea43462..6478e3c 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_conn.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_conn.erl @@ -7,12 +7,13 @@ %%% @end -module(zx_conn). +-vsn("0.2.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). -export([subscribe/2, unsubscribe/2, request/3]). --export([start/1, stop/1]). +-export([start/1, retire/1, stop/1]). -export([start_link/1, init/2]). -include("zx_logger.hrl"). @@ -67,6 +68,13 @@ start(Target) -> zx_conn_sup:start_conn(Target). +-spec retire(Conn :: pid()) -> ok. + +retire(Conn) -> + Conn ! retire, + ok. + + -spec stop(Conn :: pid()) -> ok. %% @doc %% Signals the connection to disconnect and retire immediately. @@ -114,7 +122,8 @@ connect(Parent, Debug, {Host, Port}) -> {ok, Socket} -> confirm_service(Parent, Debug, Socket); {error, Error} -> - ok = log(warning, "Connection problem with ~160tp: ~160tp", [Host, Error]), + HS = zx_net:host_string({Host, Port}), + ok = log(warning, "Connection problem with ~ts: ~160tp", [HS, Error]), ok = zx_daemon:report(failed), terminate() end. @@ -148,11 +157,21 @@ confirm_service(Parent, Debug, Socket) -> {tcp, Socket, <<2:8, Version:16>>} -> ok = zx_daemon:report({use_version, Version}), terminate(); - {tcp, Socket, <<3:8, Reason:16>>} -> + {tcp, Socket, <<3:8, Reason/utf8>>} -> ok = zx_daemon:report({no_service, Reason}), terminate(); {tcp_closed, Socket} -> - handle_unexpected_close() + ok = zx_daemon:report(failed), + terminate(); + retire -> + ok = zx_net:disconnect(Socket), + ok = zx_daemon:report(retired), + terminate(); + stop -> + ok = zx_net:disconnect(Socket), + terminate(); + Other -> + log(error, "Received: ~tp", [Other]) after 5000 -> handle_timeout(Socket) end. @@ -198,11 +217,17 @@ loop(Parent, Debug, Socket) -> {unsubscribe, Package} -> ok = do_unsubscribe(Socket, Package), loop(Parent, Debug, Socket); + {tcp_closed, Socket} -> + ok = log(info, "Connection closed unexpectedly."), + ok = zx_daemon:report(disconnected), + terminate(); + retire -> + ok = zx_net:disconnect(Socket), + ok = zx_daemon:report(retired), + terminate(); stop -> ok = zx_net:disconnect(Socket), terminate(); - {tcp_closed, Socket} -> - handle_unexpected_close(); Unexpected -> ok = log(warning, "Unexpected message: ~160tp", [Unexpected]), loop(Parent, Debug, Socket) @@ -255,25 +280,25 @@ wait_ok(Socket) -> dispatch(Socket, ID, Action) -> case Action of - {list, R} -> - send_query(Socket, <<0:1, 3:7, (term_to_binary(R))/binary>>); - {list, R, N} -> - send_query(Socket, <<0:1, 4:7, (term_to_binary({R, N}))/binary>>); - {list, R, N, V} -> - send_query(Socket, <<0:1, 4:7, (term_to_binary({R, N, V}))/binary>>); - {latest, R, N} -> - send_query(Socket, <<0:1, 5:7, (term_to_binary({R, N}))/binary>>); - {latest, R, N, V} -> - send_query(Socket, <<0:1, 5:7, (term_to_binary({R, N, V}))/binary>>); - {fetch, R, N, V} -> - make_fetch(Socket, ID, {R, N, V}); - _ -> + {list, R} -> send_query(Socket, 3, R); + {list, R, N} -> send_query(Socket, 4, {R, N}); + {list, R, N, V} -> send_query(Socket, 4, {R, N, V}); + {latest, R, N} -> send_query(Socket, 5, {R, N}); + {latest, R, N, V} -> send_query(Socket, 5, {R, N, V}); + {provides, R, M} -> send_query(Socket, 6, {R, M}); + {list_deps, R, N, V} -> send_query(Socket, 7, {R, N, V}); + {list_sysops, R} -> send_query(Socket, 8, R); + {fetch, R, N, V} -> fetch(Socket, ID, {R, N, V}); + {keychain, R, K} -> send_query(Socket, 10, {R, K}); + Unexpected -> Message = "Received unexpected request action. ID: ~tp, Action: ~200tp", - log(warning, Message, [ID, Action]) + log(warning, Message, [ID, Unexpected]) end. -send_query(Socket, Message) -> +send_query(Socket, Command, Payload) -> + TermBin = term_to_binary(Payload), + Message = <<0:1, Command:7, TermBin/binary>>, ok = gen_tcp:send(Socket, Message), wait_query(Socket). @@ -298,38 +323,39 @@ pong(Socket) -> gen_tcp:send(Socket, <<1:1, 0:7>>). --spec make_fetch(Socket, ID, PackageID) -> Result +-spec fetch(Socket, ID, PackageID) -> Result when Socket :: gen_tcp:socket(), ID :: zx_daemon:id(), PackageID :: zx:package_id(), - Result :: {done, binary()}. + Result :: {done, binary()} + | {error, Reason :: term()}. %% @private %% Download a package to the local cache. -make_fetch(Socket, ID, PackageID) -> - TermBin = term_to_binary(PackageID), - Message = <<0:1, 6:7, TermBin/binary>>, +fetch(Socket, ID, PackageID) -> + PIDB = term_to_binary(PackageID), + Message = <<0:1, 9:7, PIDB/binary>>, ok = gen_tcp:send(Socket, Message), - ok = wait_hops(Socket, ID), - {ok, Bin} = zx_net:rx(Socket), - {done, Bin}. + case wait_hops(Socket, ID, PIDB) of + ok -> + {ok, Bin} = zx_net:rx(Socket), + {done, Bin}; + Error -> + Error + end. -wait_hops(Socket, ID) -> +wait_hops(Socket, ID, PIDB) -> ok = inet:setopts(Socket, [{active, once}]), receive - {tcp, Socket, <<0:1, 0:7, 0:8>>} -> + {tcp, Socket, <<1:1, 3:7, 0:8, PIDB/binary>>} -> ok; - {tcp, Socket, <<0:1, 0:7, Distance:8>>} -> + {tcp, Socket, <<1:1, 3:7, Distance:8, PIDB/binary>>} -> ok = zx_daemon:result(ID, {hops, Distance}), - wait_hops(Socket, ID); - {tcp, Socket, <<0:1, 2:7>>} -> - handle_timeout(Socket); + wait_hops(Socket, ID, PIDB); {tcp, Socket, Bin} -> Reason = zx_net:err_in(Bin), - ok = log(error, "Failed in wait_hops/2 with reason: ~tp", [Reason]), - ok = zx_net:disconnect(Socket), - terminate() + {error, Reason} after 60000 -> handle_timeout(Socket) end. @@ -338,14 +364,6 @@ wait_hops(Socket, ID) -> %%% Terminal handlers --spec handle_unexpected_close() -> no_return(). - -handle_unexpected_close() -> - ok = log(info, "Connection closed unexpectedly."), - ok = zx_daemon:report(disconnected), - terminate(). - - -spec handle_timeout(gen_tcp:socket()) -> no_return(). handle_timeout(Socket) -> diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_conn_sup.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_conn_sup.erl similarity index 99% rename from zomp/lib/otpr/zx/0.1.0/src/zx_conn_sup.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_conn_sup.erl index f42ab01..74cf55d 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_conn_sup.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_conn_sup.erl @@ -5,6 +5,7 @@ %%% @end -module(zx_conn_sup). +-vsn("0.2.0"). -behavior(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_daemon.erl similarity index 54% rename from zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_daemon.erl index 0325c95..7e0178f 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_daemon.erl @@ -5,7 +5,7 @@ %%% %%% The daemon resides in the background once started and awaits query requests and %%% subscriptions from other processes. The daemon is only capable of handling -%%% unprivileged ("leaf") actions. +%%% unprivileged ("leaf") actions and local operations. %%% %%% %%% Discrete state and local abstract data types @@ -13,15 +13,14 @@ %%% The daemon must keep track of requestors, subscribers, peers, and zx_conn processes %%% by using monitors. Because these various types of clients are found in different %%% structures the monitors are maintained in a data type called monitor_index(), -%%% shortened to "mx" throughout the module. This structure is treated as an opaque +%%% shortened to "mx" throughout the module. This structure is treated as an abstract %%% data type and is handled by a set of functions defined toward the end of the module %%% as mx_*/N. %%% %%% Node connections (zx_conn processes) must also be tracked for status and realm %%% availability. This is done using a type called conn_index(), shortened to "cx" -%%% throughout the module. conn_index() is treated as an abstract, opaque datatype -%%% throughout the module and is handled via a set of cx_*/N functions (after the -%%% mx_*/N section). +%%% throughout the module. conn_index() is treated as an abstract datatype throughout +%%% the module and is handled via a set of cx_*/N functions (after the mx_*/N section). %%% %%% Do NOT directly access data within these structures, use (or write) an accessor %%% function that does what you want. Accessor functions MUST be pure with the @@ -139,18 +138,27 @@ %%% @end -module(zx_daemon). +-vsn("0.2.0"). -behavior(gen_server). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). --export([declare_proxy/0]). +-export([zomp_mode/0]). -export([pass_meta/3, subscribe/1, unsubscribe/1, list/0, list/1, list/2, list/3, latest/1, - verify_key/1, fetch/1, install/1, build/1]). + provides/2, list_deps/1, + list_sysops/1, + fetch/1, install/1, build/1]). +-export([register_key/2, get_key/2, keybin/2, + find_keypair/1, have_key/2, list_keys/2]). -export([report/1, result/2, notify/2]). --export([start_link/0, init_connections/0, stop/0]). +-export([connect/0, disconnect/0]). +-export([conf/1, conf/2, hosts/0, + add_mirror/1, drop_mirror/1, + takeover/1, abdicate/1, drop_realm/1]). +-export([start_link/0, idle/0]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]). @@ -168,57 +176,84 @@ %%% Type Definitions -record(s, - {meta = none :: none | zx:package_meta(), - home = none :: none | file:filename(), - argv = none :: none | [string()], - id = 0 :: id(), - actions = [] :: [request()], - requests = maps:new() :: requests(), - dropped = maps:new() :: requests(), - mx = mx_new() :: monitor_index(), - cx = cx_load() :: conn_index()}). + {meta = none :: none | zx:package_meta(), + home = none :: none | file:filename(), + argv = none :: none | [string()], + conf = load_conf() :: conf(), + id = 0 :: id(), + actions = [] :: [request()], + requests = maps:new() :: requests(), + dropped = maps:new() :: requests(), + timer = none :: none | reference(), + mx = mx_new() :: monitor_index(), + cx = new_cx() :: conn_index(), + kx = load_kx() :: key_index()}). +-record(conf, + {realms = zx_lib:list_realms() :: [zx:realm()], + managed = sets:new() :: sets:set(zx:realm()), + mirrors = sets:new() :: sets:set(zx:host()), + timeout = 5 :: pos_integer(), + retries = 3 :: non_neg_integer(), + maxconn = 5 :: pos_integer(), + status = listen :: listen | ignore, + listen_port = 11311 :: inet:port_number(), + public_port = 11311 :: inet:port_number(), + node_max = 16#10 :: pos_integer(), + vamp_max = 16#10 :: pos_integer(), + leaf_max = 16#100 :: pos_integer()}). -record(cx, - {realms = #{} :: #{zx:realm() := realm_meta()}, - attempts = [] :: [{pid(), zx:host(), [zx:realm()]}], - conns = [] :: [connection()]}). - + {realms = #{} :: #{zx:realm() := realm_meta()}, + conns = [] :: [connection()], + mirrors = queue:new() :: queue:queue(zx:host()), + hosts = queue:new() :: queue:queue(zx:host())}). -record(rmeta, {serial = 0 :: non_neg_integer(), - retries = 0 :: non_neg_integer(), prime = {"zomp.tsuriai.jp", 11311} :: zx:host(), - private = [] :: [zx:host()], - mirrors = queue:new() :: queue:queue(zx:host()), key = [] :: zx:key_name(), sysop = none :: zx:user_name(), - assigned = none :: none | pid(), + assigned = none :: none | managed | pid(), available = [] :: [pid()]}). - -record(conn, {pid = none :: none | pid(), - host = none :: none | zx:host(), realms = [] :: [zx:realm()], requests = [] :: [id()], subs = [] :: [{pid(), zx:package()}]}). +-record(key, + {pubhash = none :: none | binary(), + pub = none :: none | public_key:public_key(), + keyhash = none :: none | binary(), + key = none :: none | public_key:private_key()}). + %% State Types -type state() :: #s{}. --opaque id() :: non_neg_integer(). +-opaque id() :: integer(). +-type conf() :: #conf{}. -type request() :: {subscribe, pid(), zx:package()} | {unsubscribe, pid(), zx:package()} | {request, pid(), id(), action()}. -type requests() :: #{id() := {pid(), action()}}. +-type key_index() :: {rk_index(), pair_index(), key_owners()}. +-type rk_index() :: #{zx:realm() := key_registry()}. +-type pair_index() :: #{zx:key_hash() := zx:key_id()}. +-type key_registry() :: #{zx:key_hash() := #key{}}. +-type key_owners() :: #{zx:user_id() := {[zx:key_hash()], [zx:key_hash()]}}. -type monitor_index() :: #{pid() := {reference(), category()}}. --type conn_index() :: #cx{}. +-type conn_index() :: #cx{} | zomp | proxy. -type realm_meta() :: #rmeta{}. -type connection() :: #conn{}. -type category() :: {Reqs :: [id()], Subs :: [zx:package()]} | attempt | conn. +-type attribute() :: realms | managed | mirrors + | timeout | retries | maxconn + | status | listen_port | public_port + | node_max | vamp_max | leaf_max. %% Conn Communication -type conn_report() :: {connected, Realms :: [{zx:realm(), zx:serial()}]} @@ -236,10 +271,13 @@ | {list, zx:realm(), zx:name(), zx:version()} | {latest, zx:realm(), zx:name()} | {latest, zx:realm(), zx:name(), zx:version()} + | {provides, zx:realm(), string()} + | {list_deps, zx:realm(), zx:name(), zx:version()} + | {list_sysops, zx:realm()} | {pending, zx:realm(), zx:name()} | {approved, zx:realm()} | {fetch, zx:realm(), zx:name(), zx:version()} - | {fetchkey, zx:realm(), zx:key_name()}. + | {keychain, zx:realm(), zx:key_name()}. % Outgoing Result Messages % @@ -290,10 +328,10 @@ %%% Zomp Interface --spec declare_proxy() -> ok. +-spec zomp_mode() -> ok. -declare_proxy() -> - log(info, "Would be claiming this node for myself right now..."). +zomp_mode() -> + gen_server:cast(?MODULE, zomp_mode). %%% Requestor Interface @@ -340,13 +378,13 @@ unsubscribe(Package = {Realm, Name}) -> gen_server:cast(?MODULE, {unsubscribe, self(), Package}). --spec list() -> realm_list(). +-spec list() -> {ok, realm_list()}. %% @doc %% Request a list of currently configured realms. Because this call is entirely local %% it is the only one that does not involve a round-trip list() -> - gen_server:call(?MODULE, {request, list}). + request(list). -spec list(Realm) -> {ok, RequestID} @@ -429,21 +467,29 @@ latest({Realm, Name, Version}) -> request({latest, Realm, Name, Version}). --spec verify_key(KeyID) -> {ok, RequestID} - when KeyID :: zx:key_id(), - RequestID :: id(). -%% @doc -%% Request a public key be fetched from its upstream, and pursue the key validation -%% chain until a key in possession is found or the chain is proven to be broken. -%% Crashes the caller if either component of the KeyID is illegal. -%% -%% Response messages are of the type `result()' where the third element is of the -%% type `key_result()'. +-spec provides(Realm, Module) -> {ok, RequestID} + when Realm :: zx:realm(), + Module :: string(), + RequestID :: integer(). -verify_key({Realm, KeyName}) -> - true = zx_lib:valid_lower0_9(Realm), - true = zx_lib:valid_lower0_9(KeyName), - request({verify_key, Realm, KeyName}). +provides(Realm, Module) -> + request({provides, Realm, Module}). + + +-spec list_deps(PackageID) -> {ok, RequestID} + when PackageID :: zx:package_id(), + RequestID :: integer(). + +list_deps({Realm, Name, Version}) -> + request({list_deps, Realm, Name, Version}). + + +-spec list_sysops(Realm) -> {ok, RequestID} + when Realm :: zx:realm(), + RequestID :: integer(). + +list_sysops(Realm) -> + request({list_sysops, Realm}). -spec fetch(zx:package_id()) -> {ok, id()}. @@ -469,6 +515,99 @@ build(PackageID) -> gen_server:call(?MODULE, {build, PackageID}). +-spec add_mirror(zx:host()) -> ok. + +add_mirror(Host) -> + gen_server:cast(?MODULE, {add_mirror, Host}). + + +-spec drop_mirror(zx:host()) -> ok. + +drop_mirror(Host) -> + gen_server:cast(?MODULE, {drop_mirror, Host}). + + +-spec register_key(Owner, KeyData) -> ok + when Owner :: zx:realm() | zx:user_id(), + KeyData :: zx:key_data(). + +register_key(Owner, Data) -> + gen_server:call(?MODULE, {register_key, Owner, Data}). + + +-spec get_key(Type, KeyID) -> Result + when Type :: public | private, + KeyID :: zx:key_id(), + Result :: {ok, public_key:rsa_public_key() | public_key:rsa_private_key()} + | {error, Reason}, + Reason :: bad_realm + | no_pub + | no_key + | bad_key. + +get_key(Type, KeyID) -> + gen_server:call(?MODULE, {get_key, Type, KeyID}). + + +-spec keybin(Type, KeyID) -> Result + when Type :: public | private, + KeyID :: zx:key_id(), + Result :: {ok, binary()} + | {error, file:posix()}. + +keybin(Type, KeyID) -> + gen_server:call(?MODULE, {keybin, Type, KeyID}). + + +-spec find_keypair(KeyName) -> Result + when KeyName :: zx:key_name(), + Result :: {ok, zx:key_id()} + | error. + +find_keypair(KeyName) -> + gen_server:call(?MODULE, {find_keypair, KeyName}). + + +-spec have_key(Type, KeyID) -> boolean() + when Type :: public | private, + KeyID :: zx:key_id(). + +have_key(Type, KeyID) -> + gen_server:call(?MODULE, {have_key, Type, KeyID}). + + +-spec list_keys(Type, Owner) -> Result + when Type :: public | private, + Owner :: zx:realm() | zx:user_id(), + Result :: {ok, [zx:key_hash()]} + | {error, bad_realm | bad_user}. + +list_keys(Type, Owner) -> + gen_server:call(?MODULE, {list_keys, Type, Owner}). + + +-spec takeover(Realm) -> Result + when Realm :: zx:realm(), + Result :: ok | {error, unconfigured}. + +takeover(Realm) -> + gen_server:call(?MODULE, {takeover, Realm}). + + +-spec abdicate(Realm) -> ok + when Realm :: zx:realm(). + +abdicate(Realm) -> + gen_server:cast(?MODULE, {abdicate, Realm}). + +-spec drop_realm(Realm) -> ok + when Realm :: zx:realm(). + +drop_realm(Realm) -> + gen_server:call(?MODULE, {drop_realm, Realm}). + + + %% Request Caster -spec request(action()) -> {ok, RequestID} when RequestID :: integer(). @@ -483,10 +622,7 @@ request(Action) -> %%% Upstream Zomp connection interface -spec report(Message) -> ok - when Message :: {connected, Realms :: [{zx:realm(), zx:serial()}]} - | {redirect, Hosts :: [{zx:host(), [zx:realm()]}]} - | failed - | disconnected. + when Message :: conn_report(). %% @private %% Should only be called by a zx_conn. This function is how a zx_conn reports its %% current connection status and job results. @@ -525,29 +661,49 @@ start_link() -> -spec init(none) -> {ok, state()}. -%% @private -%% TODO: Implement lockfile checking and master lock acquisition. init(none) -> - put(zx_sys_conf, zx_sys_conf:load()), - {ok, #s{}}. + State = #s{}, + {ok, cx_load(State)}. --spec init_connections() -> ok. +-spec connect() -> ok. -init_connections() -> - gen_server:cast(?MODULE, init_connections). +connect() -> + gen_server:cast(?MODULE, connect). + + +-spec disconnect() -> ok. + +disconnect() -> + gen_server:cast(?MODULE, disconnect). + + +-spec hosts() -> {ok, [zx:host()]}. + +hosts() -> + gen_server:call(?MODULE, hosts). + + +-spec conf(attribute()) -> {ok, term()} | error. + +conf(Attribute) -> + gen_server:call(?MODULE, {conf, Attribute}). + + +-spec conf(Attribute, Value) -> ok | error + when Attribute :: attribute(), + Value :: term(). + +conf(Attribute, Value) -> + gen_server:call(?MODULE, {conf, Attribute, Value}). -%%% Shutdown +%%% (pre) Shutdown --spec stop() -> ok. -%% @doc -%% A polite way to shut down the daemon without doing a bunch of vile things. - -stop() -> - gen_server:cast(?MODULE, stop). +idle() -> + gen_server:call(?MODULE, idle). @@ -581,6 +737,50 @@ handle_call({build, PackageID}, _, State) -> Result = do_build(PackageID), NewState = eval_queue(State), {reply, Result, NewState}; +handle_call({get_key, Type, KeyID}, _, State = #s{kx = KX}) -> + Result = do_get_key(Type, KeyID, element(1, KX)), + NewState = eval_queue(State), + {reply, Result, NewState}; +handle_call({keybin, Type, KeyID}, _, State = #s{kx = KX}) -> + Result = do_keybin(Type, KeyID, element(1, KX)), + NewState = eval_queue(State), + {reply, Result, NewState}; +handle_call({find_keypair, KeyName}, _, State = #s{kx = KX}) -> + Result = maps:find(KeyName, element(2, KX)), + NewState = eval_queue(State), + {reply, Result, NewState}; +handle_call({have_key, Type, KeyID}, _, State = #s{kx = KX}) -> + Result = do_have_key(Type, KeyID, element(1, KX)), + NewState = eval_queue(State), + {reply, Result, NewState}; +handle_call({list_keys, Type, Owner}, _, State = #s{kx = KX}) -> + Result = do_list_keys(Type, Owner, KX), + NewState = eval_queue(State), + {reply, Result, NewState}; +handle_call({register_key, Owner, Data}, _, State) -> + {Result, NextState} = do_register_key(Owner, Data, State), + NewState = eval_queue(NextState), + {reply, Result, NewState}; +handle_call({takeover, Realm}, _, State = #s{conf = Conf}) -> + {Result, NewConf} = do_takeover(Realm, Conf), + NewState = eval_queue(State#s{conf = NewConf}), + {reply, Result, NewState}; +handle_call({drop_realm, Realm}, _, State = #s{conf = Conf}) -> + {Result, NewConf} = do_drop_realm(Realm, Conf), + NewState = eval_queue(State#s{conf = NewConf}), + {reply, Result, NewState}; +handle_call(hosts, _, State = #s{cx = CX}) -> + Result = cx_mirrors(CX), + {reply, Result, State}; +handle_call({conf, Attribute}, _, State = #s{conf = Conf}) -> + Result = get_conf(Attribute, Conf), + {reply, Result, State}; +handle_call({conf, Attribute, Value}, _, State = #s{conf = Conf}) -> + {Result, NewConf} = set_conf(Attribute, Value, Conf), + {reply, Result, State#s{conf = NewConf}}; +handle_call(idle, _, State) -> + ok = do_idle(State), + {reply, ok, State}; handle_call(Unexpected, From, State) -> ok = log(warning, "Unexpected call ~160tp: ~160tp", [From, Unexpected]), {noreply, State}. @@ -612,11 +812,26 @@ handle_cast({notify, Conn, Package, Update}, State) -> ok = do_notify(Conn, Package, Update, State), NewState = eval_queue(State), {noreply, NewState}; -handle_cast(init_connections, State) -> - NewState = init_connections(State), +handle_cast(connect, State) -> + NewState = connect(State), + {noreply, NewState}; +handle_cast(disconnect, State) -> + NewState = wipe_connections(State), + {noreply, NewState}; +handle_cast({add_mirror, Host}, State = #s{conf = Conf}) -> + NewConf = do_add_mirror(Host, Conf), + NewState = eval_queue(State#s{conf = NewConf}), + {noreply, NewState}; +handle_cast({drop_mirror, Host}, State = #s{conf = Conf}) -> + NewConf = do_drop_mirror(Host, Conf), + NewState = eval_queue(State#s{conf = NewConf}), + {noreply, NewState}; +handle_cast({abdicate, Realm}, State) -> + NewState = do_abdicate(Realm, State), + {noreply, NewState}; +handle_cast(zomp_mode, State) -> + NewState = become_zomp_node(State), {noreply, NewState}; -handle_cast(stop, State) -> - {stop, normal, State}; handle_cast(Unexpected, State) -> ok = log(warning, "Unexpected cast: ~160tp", [Unexpected]), {noreply, State}. @@ -625,6 +840,9 @@ handle_cast(Unexpected, State) -> %% @private %% gen_sever callback for general Erlang message handling +handle_info(reconnect, State) -> + NewState = ensure_connections(State), + {noreply, NewState#s{timer = none}}; handle_info({'DOWN', Ref, process, Pid, Reason}, State) -> NewState = clear_monitor(Pid, Ref, Reason, State), {noreply, NewState}; @@ -644,19 +862,41 @@ code_change(_, State, _) -> %% @private %% gen_server callback to handle shutdown/cleanup tasks on receipt of a clean %% termination request. -%% TODO: Implement new master selection, dequeuing and request queue passing. -terminate(normal, #s{cx = CX}) -> - ok = log(info, "zx_daemon shutting down..."), - case cx_store_cache(CX) of - ok -> - log(info, "Cache written."); - {error, Reason} -> - Message = "Cache write failed with ~160tp", - log(error, Message, [Reason]) +terminate(_, _) -> ok. + + +do_idle(#s{id = ID, cx = zomp}) -> + ok = log(info, "Idling as zomp with ID: ~p.", [ID]), + ok = retire(ID); +do_idle(#s{cx = proxy}) -> + ok = log(info, "Idling as proxy."), + ok; +do_idle(#s{id = ID, cx = CX}) -> + ok = log(info, "Idling as prime with ID: ~p.", [ID]), + ok = retire(ID), + cx_store_cache(CX). + + +retire(ID) -> + case zx_peer_man:retire(ID) of + ok -> ok; + halt -> remove_lockfile() end. +write_lockfile(Port) -> + PortString = integer_to_binary(Port), + LockFile = lockfile(), + zx_lib:write_terms(LockFile, PortString). + + +remove_lockfile() -> + zx_lib:rm_rf(lockfile()). + + +lockfile() -> + filename:join(zx_lib:zomp_dir(), "john.locke"). %%% Doer Functions @@ -707,6 +947,10 @@ do_unsubscribe(Pid, Package, State = #s{actions = Actions, mx = MX}) -> %% @private %% Enqueue requests and update relevant index. +do_request(Requestor, Action, State = #s{id = ID, cx = proxy}) -> + Result = zx_proxy:request(Action), + Requestor ! {result, ID, Result}, + State; do_request(Requestor, Action, State = #s{id = ID, actions = Actions, mx = MX}) -> NewActions = [{request, Requestor, ID, Action} | Actions], NewMX = mx_add_monitor(Requestor, {requestor, ID}, MX), @@ -722,87 +966,95 @@ do_request(Requestor, Action, State = #s{id = ID, actions = Actions, mx = MX}) - %% Receive a report from a connection process, update the connection index and %% possibly retry connections. +do_report(Conn, Report, State = #s{cx = zomp}) -> + ok = log(warning, "Zomp mode: Discarding report ~tp ~200tp", [Conn, Report]), + State; +do_report(Conn, Report, State = #s{cx = proxy}) -> + ok = log(warning, "Proxied: Discarding report ~tp ~200tp", [Conn, Report]), + State; do_report(Conn, {connected, Realms}, State = #s{mx = MX, cx = CX}) -> - NextMX = mx_upgrade_conn(Conn, MX), {NewMX, NewCX} = case cx_connected(Realms, Conn, CX) of {assigned, NextCX} -> + NextMX = mx_upgrade_conn(Conn, MX), {NextMX, NextCX}; {unassigned, NextCX} -> - ScrubbedMX = mx_del_monitor(Conn, conn, NextMX), + ScrubbedMX = mx_del_monitor(Conn, attempt, MX), ok = zx_conn:stop(Conn), {ScrubbedMX, NextCX} end, State#s{mx = NewMX, cx = NewCX}; -do_report(Conn, {redirect, Hosts}, State = #s{mx = MX, cx = CX}) -> - NextMX = mx_del_monitor(Conn, attempt, MX), - {Unassigned, NextCX} = cx_redirect(Conn, Hosts, CX), - {NewMX, NewCX} = ensure_connections(Unassigned, NextMX, NextCX), - State#s{mx = NewMX, cx = NewCX}; +do_report(Conn, {redirect, Targets}, State = #s{mx = MX, cx = CX}) -> + NewMX = mx_del_monitor(Conn, attempt, MX), + NewCX = cx_redirect(Targets, CX), + ensure_connections(State#s{mx = NewMX, cx = NewCX}); do_report(Conn, failed, State = #s{mx = MX}) -> NewMX = mx_del_monitor(Conn, attempt, MX), - failed(Conn, State#s{mx = NewMX}); -do_report(Conn, disconnected, State = #s{mx = MX}) -> - NewMX = mx_del_monitor(Conn, conn, MX), - disconnected(Conn, State#s{mx = NewMX}); -do_report(Conn, timeout, State = #s{mx = MX}) -> + ensure_connections(State#s{mx = NewMX}); +do_report(Conn, disconnected, State) -> + ok = log(info, "Connection ~160tp disconnected.", [Conn]), + recover(Conn, State); +do_report(Conn, timeout, State) -> ok = log(warning, "Connection ~160tp timed out.", [Conn]), + recover(Conn, State); +do_report(Conn, + retired, + State = #s{actions = Actions, requests = Requests, mx = MX, cx = CX}) -> + case mx_drop_monitor(Conn, MX) of + {attempt, NewMX} -> + State#s{mx = NewMX}; + {conn, NewMX} -> + {Pending, LostSubs, NewCX} = cx_disconnected(Conn, CX), + ReSubs = [{subscribe, S, P} || {S, P} <- LostSubs], + {Dequeued, NewRequests} = maps:fold(dequeue(Pending), {[], #{}}, Requests), + NewActions = Dequeued ++ ReSubs ++ Actions, + State#s{actions = NewActions, + requests = NewRequests, + mx = NewMX, + cx = NewCX} + end. + + +-spec recover(Conn, State) -> NewState + when Conn :: pid(), + State :: state(), + NewState :: state(). + +recover(Conn, State = #s{actions = Actions, requests = Requests, mx = MX, cx = CX}) -> NewMX = mx_del_monitor(Conn, conn, MX), - disconnected(Conn, State#s{mx = NewMX}). + {Pending, LostSubs, NewCX} = cx_disconnected(Conn, CX), + ReSubs = [{subscribe, S, P} || {S, P} <- LostSubs], + {Dequeued, NewRequests} = maps:fold(dequeue(Pending), {[], #{}}, Requests), + NewActions = Dequeued ++ ReSubs ++ Actions, + NewState = State#s{actions = NewActions, + requests = NewRequests, + mx = NewMX, + cx = NewCX}, + ensure_connections(NewState). --spec failed(Conn, State) -> NewState - when Conn :: pid(), - State :: state(), - NewState :: state(). - -failed(Conn, State = #s{mx = MX, cx = CX}) -> - {Realms, NextCX} = cx_failed(Conn, CX), - {NewMX, NewCX} = ensure_connections(Realms, MX, NextCX), - State#s{mx = NewMX, cx = NewCX}. - - --spec disconnected(Conn, State) -> NewState - when Conn :: pid(), - State :: state(), - NewState :: state(). - -disconnected(Conn, - State = #s{actions = Actions, requests = Requests, mx = MX, cx = CX}) -> - {Pending, LostSubs, Unassigned, ScrubbedCX} = cx_disconnected(Conn, CX), - ReSubs = [{S, {subscribe, P}} || {S, P} <- LostSubs], - {Dequeued, NewRequests} = maps:fold(dequeue(Pending), {#{}, #{}}, Requests), - ReReqs = maps:to_list(Dequeued), - NewActions = ReReqs ++ ReSubs ++ Actions, - {NewMX, NewCX} = ensure_connections(Unassigned, MX, ScrubbedCX), - State#s{actions = NewActions, - requests = NewRequests, - mx = NewMX, - cx = NewCX}. - - --spec dequeue(Pending) -> fun((K, V, {D, R}) -> {NewD, NewR}) +-spec dequeue(Pending) -> fun((ID, V, {D, R}) -> {NewD, NewR}) when Pending :: [id()], - K :: id(), - V :: request(), - D :: #{id() := request()}, - R :: #{id() := request()}, - NewD :: #{id() := request()}, - NewR :: #{id() := request()}. + ID :: id(), + V :: {pid(), action()}, + D :: [request()], + R :: requests(), + NewD :: [request()], + NewR :: requests(). %% @private %% Return a function that partitions the current Request map into two maps, one that %% matches the closed `Pending' list of references and ones that don't. dequeue(Pending) -> - fun(K, V, {D, R}) -> - case lists:member(K, Pending) of - true -> {maps:put(K, V, D), R}; - false -> {D, maps:put(K, V, R)} + fun(ID, {Pid, Action}, {D, R}) -> + case lists:member(ID, Pending) of + true -> {[{request, Pid, ID, Action} | D], R}; + false -> {D, maps:put(ID, {Pid, Action}, R)} end end. --spec init_connections(State) -> NewState +-spec connect(State) -> NewState when State :: state(), NewState :: state(). %% @private @@ -811,89 +1063,77 @@ dequeue(Pending) -> %% return a populated MX and CX to the caller. Should only ever be called by init/1. %% Returns an `ok' tuple to disambiguate it from pure functions *and* to leave an %% obvious place to populate error returns in the future if desired. -%% -%%TODO: This is a hack to make things feel managedish. They aren't. -%% A local manager process needs to exist if an actual Zomp node doesn't. -init_connections(State = #s{mx = MX, cx = CX}) -> - Realms = cx_realms(CX), - Managed = zx_sys_conf:managed(get(zx_sys_conf)), - Unmanaged = lists:subtract(Realms, Managed), - {ok, NewMX, NewCX} = init_connections(Unmanaged, MX, CX), +connect(State = #s{cx = zomp}) -> + State; +connect(State) -> + LockFile = lockfile(), + case file:read_file(LockFile) of + {ok, PS} -> + Port = binary_to_integer(string:trim(PS)), + proxy_connect(Port, State); + {error, enoent} -> + remote_connect(State) + end. + + +proxy_connect(Port, State) -> + case zx_proxy:connect(Port) of + ok -> State#s{cx = proxy}; + error -> remote_connect(State) + end. + + +remote_connect(State = #s{cx = CX}) -> + LockFile = lockfile(), + {ok, Port} = zx_peer_man:listen(), + PortString = integer_to_binary(Port), + ok = file:write_file(LockFile, PortString), + {ok, Hosts} = cx_mirrors(CX), + init_connections(Hosts, State). + + +init_connections([Host | Hosts], State = #s{mx = MX}) -> + {ok, Pid} = zx_conn:start(Host), + NewMX = mx_add_monitor(Pid, attempt, MX), + init_connections(Hosts, State#s{mx = NewMX}); +init_connections([], State) -> + State. + + +ensure_connections(State = #s{cx = zomp}) -> + State; +ensure_connections(State = #s{cx = proxy}) -> + State; +ensure_connections(State = #s{conf = Conf, mx = MX, cx = CX}) -> + #conf{realms = Realms, managed = Managed} = Conf, + Unmanaged = lists:subtract(Realms, sets:to_list(Managed)), + case cx_check_service(Unmanaged, CX) of + {ok, NewCX} -> + State#s{cx = NewCX}; + {connect, wait, NewCX} -> + ok = log(info, "Host list exhausted, retrying in 5 seconds."), + Timer = erlang:send_after(5000, self(), reconnect), + State#s{timer = Timer, cx = NewCX}; + {connect, Host, NewCX} -> + {ok, Pid} = zx_conn:start(Host), + NewMX = mx_add_monitor(Pid, attempt, MX), + State#s{mx = NewMX, cx = NewCX} + end. + + +-spec wipe_connections(state()) -> ok. + +wipe_connections(State = #s{cx = proxy}) -> + ok = log(warning, "Proxied: No connections to wipe."), + State; +wipe_connections(State = #s{mx = MX, cx = CX}) -> + {Pids, NewCX} = cx_wipe(CX), + Remove = fun(P, M) -> mx_del_monitor(P, conn, M) end, + NewMX = lists:foldl(Remove, MX, Pids), State#s{mx = NewMX, cx = NewCX}. --spec init_connections(Realms, MX, CX) -> {ok, NewMX, NewCX} - when Realms :: [zx:realm()], - MX :: monitor_index(), - CX :: conn_index(), - NewMX :: monitor_index(), - NewCX :: conn_index(). - -init_connections([Realm | Realms], MX, CX) -> - {ok, Hosts, NextCX} = cx_next_hosts(3, Realm, CX), - MaybeAttempt = - fun(Host, {M, C}) -> - case cx_maybe_add_attempt(Host, Realm, C) of - not_connected -> - {ok, Pid} = zx_conn:start(Host), - NewM = mx_add_monitor(Pid, attempt, M), - NewC = cx_add_attempt(Pid, Host, Realm, C), - {NewM, NewC}; - {ok, NewC} -> - {M, NewC} - end - end, - {NewMX, NewCX} = lists:foldl(MaybeAttempt, {MX, NextCX}, Hosts), - init_connections(Realms, NewMX, NewCX); -init_connections([], MX, CX) -> - {ok, MX, CX}. - - --spec ensure_connections(Realms, MX, CX) -> {NewMX, NewCX} - when Realms :: [zx:realm()], - MX :: monitor_index(), - CX :: conn_index(), - NewMX :: monitor_index(), - NewCX :: conn_index(). -%% @private -%% Check the list of unprovided realms with all available connections that can provide -%% it, and allocate accordingly. If any realms cannot be provided by existing -%% connections, new connections are initiated for all unprovided realms. - -ensure_connections(Realms, MX, CX) -> - {NextCX, Unavailable} = reassign_conns(Realms, CX, []), - {ok, NewMX, NewCX} = init_connections(Unavailable, MX, NextCX), - {NewMX, NewCX}. - - --spec reassign_conns(Realms, CX, Unavailable) -> {NewCX, NewUnavailable} - when Realms :: [zx:realm()], - CX :: conn_index(), - Unavailable :: [zx:realm()], - NewCX :: conn_index(), - NewUnavailable :: [zx:realm()]. -%% @private -%% Finds connections that provide a requested realm and assigns that connection to -%% take over realm provision. Returns the updated CX and a list of all the realms -%% that could not be provided by any available connection. - -reassign_conns([Realm | Realms], CX = #cx{realms = RMetas}, Unassigned) -> - {NewUnassigned, NewCX} = - case maps:get(Realm, RMetas) of - #rmeta{available = []} -> - {[Realm | Unassigned], CX}; - Meta = #rmeta{available = [Conn | Conns]} -> - NewMeta = Meta#rmeta{assigned = Conn, available = Conns}, - NewRMetas = maps:put(Realm, NewMeta, RMetas), - NextCX = CX#cx{realms = NewRMetas}, - {Unassigned, NextCX} - end, - reassign_conns(Realms, NewCX, NewUnassigned); -reassign_conns([], CX, Unassigned) -> - {CX, Unassigned}. - - -spec do_result(ID, Result, State) -> NewState when ID :: id(), Result :: result(), @@ -920,11 +1160,14 @@ do_result(ID, Result, State = #s{requests = Requests, dropped = Dropped, mx = MX State#s{requests = NewRequests, dropped = NewDropped, mx = NewMX}. -handle_fetch_result(ID, {done, Bin}, {Requestor, _}, Requests, MX) -> +handle_fetch_result(ID, {done, Bin}, {Requestor, {fetch, R, N, V}}, Requests, MX) -> Result = case do_import_package(Bin) of - ok -> done; - Error -> Error + ok -> + ok = file:write_file(zx_lib:zsp_path({R, N, V}), Bin), + done; + Error -> + Error end, Requestor ! {result, ID, Result}, NextMX = mx_del_monitor(Requestor, {requestor, ID}, MX), @@ -988,6 +1231,8 @@ do_notify(Conn, Channel, Message, #s{cx = CX}) -> %% This function must iterate as far as it can into the request queue, adding response %% entries to the pending response structure as it goes. +eval_queue(State = #s{cx = proxy}) -> + State; eval_queue(State = #s{actions = Actions}) -> InOrder = lists:reverse(Actions), eval_queue(InOrder, State#s{actions = []}). @@ -1005,8 +1250,61 @@ eval_queue(State = #s{actions = Actions}) -> eval_queue([], State) -> State; -eval_queue([Action = {request, Pid, ID, Message} | Rest], - State = #s{actions = Actions, requests = Requests, cx = CX}) -> +eval_queue(Actions, State = #s{cx = zomp}) -> + local_dispatch(Actions, State); +eval_queue(Actions, State) -> + remote_dispatch(Actions, State). + + +local_dispatch([], State) -> + State; +local_dispatch([{request, Pid, ID, Message} | Rest], State) -> + Realm = element(2, Message), + Result = + case zomp_realm_man:lookup(Realm) of + {ok, RealmPid} -> + Stripped = erlang:delete_element(2, Message), + local_request(RealmPid, Stripped); + Error -> + Error + end, + Pid ! {result, ID, Result}, + local_dispatch(Rest, State); +local_dispatch([{subscribe, Pid, RP = {Realm, Package}} | Rest], State) -> + case zomp_realm_man:lookup(Realm) of + {ok, RealmP} -> + {ok, Serial} = zomp_realm:subscribe(RealmP, Package), + Pid ! {notify, subscribed, RP, Serial}; + Error -> + Pid ! {notify, error, RP, Error} + end, + local_dispatch(Rest, State); +local_dispatch([{unsubscribe, Pid, {Realm, Package}} | Rest], State) -> + ok = + case zomp_realm_man:lookup(Realm) of + {ok, RealmP} -> + zomp_realm:unsubscribe(RealmP, Package); + Error -> + Message = "Unsubscribe ~tp from ~tp failed with ~tp", + log(warning, Message, [Pid, {Realm, Package}, Error]) + end, + local_dispatch(Rest, State). + + +local_request(R, {list}) -> zomp_realm:list(R); +local_request(R, {list, N}) -> zomp_realm:list(R, N); +local_request(R, {list, N, V}) -> zomp_realm:list(R, {N, V}); +local_request(R, {latest, N}) -> zomp_realm:latest(R, N); +local_request(R, {latest, N, V}) -> zomp_realm:latest(R, {N, V}); +local_request(R, {provides, M}) -> zomp_realm:provides(R, M); +local_request(R, {list_deps, N, V}) -> zomp_realm:list_deps(R, {N, V}); +local_request(R, {list_sysops}) -> zomp_realm:list_sysops(R). + + +remote_dispatch([], State) -> + State; +remote_dispatch([Action = {request, Pid, ID, Message} | Rest], + State = #s{actions = Actions, requests = Requests, cx = CX}) -> {NewActions, NewRequests, NewCX} = case dispatch_request(Message, ID, CX) of {dispatched, NextCX} -> @@ -1016,12 +1314,12 @@ eval_queue([Action = {request, Pid, ID, Message} | Rest], NextActions = [Action | Actions], {NextActions, Requests, CX}; Result -> - Pid ! Result, + Pid ! {result, ID, Result}, {Actions, Requests, CX} end, NewState = State#s{actions = NewActions, requests = NewRequests, cx = NewCX}, - eval_queue(Rest, NewState); -eval_queue([Action = {subscribe, Pid, Package} | Rest], + remote_dispatch(Rest, NewState); +remote_dispatch([Action = {subscribe, Pid, Package} | Rest], State = #s{actions = Actions, cx = CX}) -> {NewActions, NewCX} = case cx_add_sub(Pid, Package, CX) of @@ -1036,8 +1334,8 @@ eval_queue([Action = {subscribe, Pid, Package} | Rest], Pid ! {z_sub, Package, {error, bad_realm}}, {Actions, CX} end, - eval_queue(Rest, State#s{actions = NewActions, cx = NewCX}); -eval_queue([{unsubscribe, Pid, Package} | Rest], State = #s{cx = CX}) -> + remote_dispatch(Rest, State#s{actions = NewActions, cx = NewCX}); +remote_dispatch([{unsubscribe, Pid, Package} | Rest], State = #s{cx = CX}) -> NewCX = case cx_del_sub(Pid, Package, CX) of {{drop_sub, ConnPid}, NextCX} -> @@ -1052,11 +1350,11 @@ eval_queue([{unsubscribe, Pid, Package} | Rest], State = #s{cx = CX}) -> ok = log(warning, M, [Package]), CX end, - eval_queue(Rest, State#s{cx = NewCX}). + remote_dispatch(Rest, State#s{cx = NewCX}). --spec dispatch_request(Action, ID, CX) -> Result - when Action :: action(), +-spec dispatch_request(Message, ID, CX) -> Result + when Message :: action(), ID :: id(), CX :: conn_index(), Result :: {dispatched, NewCX} @@ -1071,17 +1369,16 @@ eval_queue([{unsubscribe, Pid, Package} | Rest], State = #s{cx = CX}) -> %% configured at all, the process is short-circuited by forming an error response %% directly. -dispatch_request(Action, ID, CX) -> - Realm = element(2, Action), +dispatch_request(Message, ID, CX) -> + Realm = element(2, Message), case cx_pre_send(Realm, ID, CX) of {ok, Conn, NewCX} -> - ok = zx_conn:request(Conn, ID, Action), + ok = zx_conn:request(Conn, ID, Message), {dispatched, NewCX}; unassigned -> wait; unconfigured -> - Error = {error, bad_realm}, - {result, ID, Error} + {error, bad_realm} end. @@ -1102,11 +1399,11 @@ clear_monitor(Pid, dropped = Dropped, mx = MX, cx = CX}) -> - case mx_crashed_monitor(Pid, MX) of + case mx_drop_monitor(Pid, MX) of {attempt, NewMX} -> - failed(Pid, State#s{mx = NewMX}); + ensure_connections(State#s{mx = NewMX}); {conn, NewMX} -> - disconnected(Pid, State#s{mx = NewMX}); + recover(Pid, State#s{mx = NewMX}); {{Reqs, Subs}, NewMX} -> NewActions = drop_actions(Pid, Actions), {NewDropped, NewRequests} = drop_requests(Reqs, Dropped, Requests), @@ -1131,7 +1428,7 @@ clear_monitor(Pid, drop_actions(Pid, Actions) -> Clear = fun - ({request, P, _}) -> P /= Pid; + ({request, P, _, _}) -> P /= Pid; ({subscribe, P, _}) -> P /= Pid; ({unsubscribe, _, _}) -> false end, @@ -1148,9 +1445,13 @@ drop_actions(Pid, Actions) -> drop_requests(ReqIDs, Dropped, Requests) -> Partition = fun(K, {Drop, Keep}) -> - {V, NewKeep} = maps:take(K, Keep), - NewDrop = maps:put(K, V, Drop), - {NewDrop, NewKeep} + case maps:take(K, Keep) of + {V, NewKeep} -> + NewDrop = maps:put(K, V, Drop), + {NewDrop, NewKeep}; + error -> + {Drop, Keep} + end end, lists:foldl(Partition, {Dropped, Requests}, ReqIDs). @@ -1164,6 +1465,8 @@ drop_requests(ReqIDs, Dropped, Requests) -> %% Provide a chance to bypass if the package is in cache. do_fetch(PackageID, Requestor, State = #s{id = ID}) -> + {ok, PackageString} = zx_lib:package_string(PackageID), + ok = log(info, "Fetching ~ts", [PackageString]), Path = zx_lib:zsp_path(PackageID), case file:read_file(Path) of {ok, Bin} -> @@ -1216,8 +1519,8 @@ do_import_package(Bin) -> -spec import_package(binary()) -> no_return(). %% @private %% The happy path of .zsp installation. -%% Must NEVER be executed by the zx_daemon directly. - +%% Must not be executed by the zx_daemon directly. +%% %% More generally, there are a few phases: %% 1- Loading the binary to extract the PackageID %% 2- Checking the signature @@ -1239,19 +1542,10 @@ do_import_package(Bin) -> %% If a place to get more fancy with the phases becomes really obvious after writing %% identicalish segements of functions a few places then I'll break things apart. -import_package(Bin = <>) -> - <> = Signed, - {ok, {PackageID, SigKeyName, _, _}} = zx_lib:b_to_ts(MetaBin), - {ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}), - true = zx_key:verify(Signed, Sig, PubKey), - ok = file:write_file(zx_lib:zsp_path(PackageID), Bin), - Destination = zx_lib:ppath(lib, PackageID), - ok = filelib:ensure_dir(Destination), - ok = zx_lib:rm_rf(Destination), - ok = file:make_dir(Destination), - Result = erl_tar:extract({binary, TarGZ}, [{cwd, Destination}, compressed]), +import_package(ZspBin) -> + Result = zx_zsp:extract(ZspBin, lib), zx_daemon ! {self(), Result}. - + -spec do_build(zx:package_id()) -> zx:outcome(). %% @private @@ -1283,6 +1577,530 @@ make(PackageID) -> zx_daemon ! {self(), Result}. + +%%% Config Functions + +load_conf() -> + Path = conf_path(), + case file:consult(Path) of + {ok, Settings} -> + populate_conf(Settings); + {error, Reason} -> + ok = log(error, "Load ~160tp failed with: ~160tp", [Path, Reason]), + Data = #conf{}, + ok = save_conf(Data), + Data + end. + + +populate_conf(Settings) -> + Timeout = + case proplists:get_value(timeout, Settings, 5) of + TO when is_integer(TO) and TO > 0 -> TO; + _ -> 5 + end, + Retries = + case proplists:get_value(retries, Settings, 3) of + RT when is_integer(RT) and RT > 0 -> RT; + _ -> 3 + end, + MaxConn = + case proplists:get_value(maxconn, Settings, 5) of + MC when is_integer(MC) and MC > 0 -> MC; + _ -> 5 + end, + Managed = + case proplists:get_value(managed, Settings, []) of + MN when is_list(MN) -> sets:from_list(MN); + _ -> sets:new() + end, + Mirrors = + case proplists:get_value(mirrors, Settings, []) of + MR when is_list(MR) -> sets:from_list(MR); + _ -> sets:new() + end, + Status = + case proplists:get_value(status, Settings, listen) of + listen -> listen; + ignore -> ignore; + _ -> listen + end, + ListenPort = + case proplists:get_value(listen_port, Settings, 11311) of + LP when is_integer(LP) and (LP > 0) and (LP < 16#10000) -> LP; + _ -> 11311 + end, + PublicPort = + case proplists:get_value(listen_port, Settings, 11311) of + PP when is_integer(PP) and (PP > 0) and (PP < 16#10000) -> PP; + _ -> 11311 + end, + NodeMax = + case proplists:get_value(node_max, Settings, 16#10) of + NM when is_integer(NM) and (NM >= 0) -> NM; + _ -> 16#10 + end, + VampMax = + case proplists:get_value(vamp_max, Settings, 16#10) of + VM when is_integer(VM) and (VM >= 0) -> VM; + _ -> 16#10 + end, + LeafMax = + case proplists:get_value(leaf_max, Settings, 16#100) of + LM when is_integer(LM) and (LM >= 0) -> LM; + _ -> 16#100 + end, + #conf{timeout = Timeout, + retries = Retries, + maxconn = MaxConn, + managed = Managed, + mirrors = Mirrors, + status = Status, + listen_port = ListenPort, + public_port = PublicPort, + node_max = NodeMax, + vamp_max = VampMax, + leaf_max = LeafMax}. + + +-spec save_conf(conf()) -> ok. +%% @doc +%% Save the current etc/sys.conf to disk. + +save_conf(#conf{timeout = Timeout, + retries = Retries, + maxconn = MaxConn, + managed = Managed, + mirrors = Mirrors, + status = Status, + listen_port = ListenPort, + public_port = PublicPort, + node_max = NodeMax, + vamp_max = VampMax, + leaf_max = LeafMax}) -> + Terms = + [{timeout, Timeout}, + {retries, Retries}, + {maxconn, MaxConn}, + {managed, sets:to_list(Managed)}, + {mirrors, sets:to_list(Mirrors)}, + {status, Status}, + {listen_port, ListenPort}, + {public_port, PublicPort}, + {node_max, NodeMax}, + {vamp_max, VampMax}, + {leaf_max, LeafMax}], + Path = conf_path(), + ok = zx_lib:write_terms(Path, Terms), + log(info, "Wrote etc/sys.conf"). + + +-spec conf_path() -> file:filename(). +%% @private +%% Return the path to $ZOMP_DIR/etc/sys.conf. + +conf_path() -> + filename:join(zx_lib:path(etc), "sys.conf"). + + +-spec get_conf(attribute(), conf()) -> {ok, term()} | error. + +get_conf(realms, #conf{realms = V}) -> {ok, V}; +get_conf(managed, #conf{managed = V}) -> {ok, sets:to_list(V)}; +get_conf(mirrors, #conf{mirrors = V}) -> {ok, sets:to_list(V)}; +get_conf(timeout, #conf{timeout = V}) -> {ok, V}; +get_conf(retries, #conf{retries = V}) -> {ok, V}; +get_conf(maxconn, #conf{maxconn = V}) -> {ok, V}; +get_conf(status, #conf{status = V}) -> {ok, V}; +get_conf(listen_port, #conf{listen_port = V}) -> {ok, V}; +get_conf(public_port, #conf{public_port = V}) -> {ok, V}; +get_conf(node_max, #conf{node_max = V}) -> {ok, V}; +get_conf(vamp_max, #conf{vamp_max = V}) -> {ok, V}; +get_conf(leaf_max, #conf{leaf_max = V}) -> {ok, V}; +get_conf(_, _) -> error. + + +-spec set_conf(Attribute, Value, Conf) -> Result + when Attribute :: attribute(), + Value :: term(), + Conf :: conf(), + Result :: {ok, NewConf} + | {error, Conf}, + NewConf :: conf(). + +set_conf(managed, V, C) -> {ok, C#conf{managed = V}}; +set_conf(mirrors, V, C) -> {ok, C#conf{mirrors = V}}; +set_conf(timeout, V, C) -> {ok, C#conf{timeout = V}}; +set_conf(retries, V, C) -> {ok, C#conf{retries = V}}; +set_conf(maxconn, V, C) -> {ok, C#conf{maxconn = V}}; +set_conf(status, V, C) -> {ok, C#conf{status = V}}; +set_conf(listen_port, V, C) -> {ok, C#conf{listen_port = V}}; +set_conf(public_port, V, C) -> {ok, C#conf{public_port = V}}; +set_conf(node_max, V, C) -> {ok, C#conf{node_max = V}}; +set_conf(vamp_max, V, C) -> {ok, C#conf{vamp_max = V}}; +set_conf(leaf_max, V, C) -> {ok, C#conf{leaf_max = V}}; +set_conf(_, _, C) -> {error, C}. + + +-spec do_add_mirror(Host, Conf) -> NewConf + when Host :: zx:host(), + Conf :: conf(), + NewConf :: conf(). + +do_add_mirror(Host, C = #conf{mirrors = Mirrors}) -> + NewMirrors = sets:add_element(Host, Mirrors), + NewC = C#conf{mirrors = NewMirrors}, + ok = save_conf(NewC), + NewC. + + +-spec do_drop_mirror(Host, Conf) -> NewConf + when Host :: zx:host(), + Conf :: conf(), + NewConf :: conf(). + +do_drop_mirror(Host, C = #conf{mirrors = Mirrors}) -> + NewMirrors = sets:del_element(Host, Mirrors), + NewC = C#conf{mirrors = NewMirrors}, + ok = save_conf(NewC), + NewC. + + +-spec do_takeover(Realm, Conf) -> {Result, NewConf} + when Realm :: zx:realm(), + Conf :: conf(), + Result :: ok + | {error, unconfigured}, + NewConf:: conf(). +%% @private +%% Assume responsibilities as the prime node for the given realm. Only works if +%% the realm exists, of course. + +do_takeover(Realm, C = #conf{realms = Realms, managed = Managed}) -> + case lists:member(Realm, Realms) of + true -> + NewManaged = sets:add_element(Realm, Managed), + NewC = C#conf{managed = NewManaged}, + ok = save_conf(NewC), + ok = log(info, "Managing realm: ~160tp", [Realm]), + {ok, NewC}; + false -> + ok = log(warning, "Cannot manage unconfigured realm: ~160tp", [Realm]), + {{error, unconfigured}, C} + end. + + +-spec do_abdicate(Realm, Conf) -> {Result, NewConf} + when Realm :: zx:realm(), + Conf :: conf(), + Result :: ok + | {error, unmanaged}, + NewConf :: conf(). + +do_abdicate(Realm, C = #conf{managed = Managed}) -> + case sets:is_element(Realm, Managed) of + true -> + NewManaged = sets:del_element(Realm, Managed), + NewC = C#conf{managed = NewManaged}, + ok = save_conf(NewC), + ok = log(info, "No longer managing realm: ~160tp", [Realm]), + {ok, NewC}; + false -> + ok = log(error, "Cannot abdicate an unmanaged realm."), + {{error, unmanaged}, C} + end. + + +%%% Key Functions + +-spec load_kx() -> key_index(). + +load_kx() -> + case file:read_file(key_stash_path()) of + {ok, Bin} -> binary_to_term(Bin); + {error, enoent} -> {maps:new(), maps:new(), maps:new()} + end. + +key_stash_path() -> + filename:join(zx_lib:path(key), "key.stash"). + + +-spec do_register_key(Owner, KeyData, State) -> {ok, NewState} + when Owner :: zx:realm() | zx:user_id(), + KeyData :: zx:key_data(), + State :: state(), + NewState :: state(). + +do_register_key(UserID = {Realm, _}, Data, State = #s{kx = {RX, PX, UK}}) -> + {NewRX, NewPX} = do_register_realm_key(Realm, Data, RX, PX), + NewUK = + case Data of + {PairHash, none, {_, K}} when is_binary(K) -> + Update = fun({Ps, Ks}) -> {Ps, [PairHash | Ks]} end, + maps:update_with(UserID, Update, {[], [PairHash]}, UK); + {PairHash, {_, P}, none} when is_binary(P) -> + Update = fun({Ps, Ks}) -> {[PairHash | Ps], Ks} end, + maps:update_with(UserID, Update, {[PairHash], []}, UK); + {PairHash, {_, P}, {_, K}} when is_binary(P), is_binary(K) -> + Update = fun({Ps, Ks}) -> {[PairHash | Ps], [PairHash | Ks]} end, + maps:update_with(UserID, Update, {[PairHash], [PairHash]}, UK) + end, + NewKX = {NewRX, NewPX, NewUK}, + ok = stash_keys(NewKX), + {ok, State#s{kx = NewKX}}; +do_register_key(Realm, Data, State = #s{kx = {RX, PX, UK}}) -> + {NewRX, NewPX} = do_register_realm_key(Realm, Data, RX, PX), + NewKX = {NewRX, NewPX, UK}, + ok = stash_keys(NewKX), + {ok, State#s{kx = NewKX}}. + +do_register_realm_key(Realm, Data, RealmIndex, PairIndex) -> + PairHash = element(1, Data), + {K, Pairs} = do_register_realm_key2(Realm, Data), + KeyRegistry = maps:get(Realm, RealmIndex, #{}), + NewKeyRegistry = update_registry(PairHash, K, KeyRegistry), + NewRealmIndex = maps:put(Realm, NewKeyRegistry, RealmIndex), + NewPairIndex = maps:merge(PairIndex, Pairs), + {NewRealmIndex, NewPairIndex}. + +do_register_realm_key2(Realm, {PairHash, {_, PubBin}, none}) -> + PubHash = crypto:hash(sha512, PubBin), + ok = store_key(public, {Realm, PairHash}, PubBin), + Pub = public_key:der_decode('RSAPublicKey', PubBin), + K = #key{pubhash = PubHash, pub = Pub}, + Pairs = #{PubHash => PairHash}, + {K, Pairs}; +do_register_realm_key2(Realm, {PairHash, none, {_, KeyBin}}) -> + KeyHash = crypto:hash(sha512, KeyBin), + ok = store_key(private, {Realm, PairHash}, KeyBin), + Key = public_key:der_decode('RSAPrivateKey', KeyBin), + K = #key{keyhash = KeyHash, key = Key}, + Pairs = #{KeyHash => PairHash}, + {K, Pairs}; +do_register_realm_key2(Realm, {PairHash, {_, PubBin}, {_, KeyBin}}) -> + PubHash = crypto:hash(sha512, PubBin), + KeyHash = crypto:hash(sha512, KeyBin), + ok = store_key(public, {Realm, PairHash}, PubBin), + ok = store_key(private, {Realm, PairHash}, KeyBin), + PairHash = crypto:hash(sha512, <>), + Pub = public_key:der_decode('RSAPublicKey', PubBin), + Key = public_key:der_decode('RSAPrivateKey', KeyBin), + K = #key{pubhash = PubHash, pub = Pub, keyhash = KeyHash, key = Key}, + Pairs = #{PubHash => PairHash, KeyHash => PairHash}, + {K, Pairs}. + +store_key(Type, KeyID, Bin) -> + Path = zx_key:path(Type, KeyID), + ok = filelib:ensure_dir(Path), + file:write_file(Path, Bin). + +update_registry(PairHash, + K = #key{pubhash = none, keyhash = KeyHash, key = Key}, + Registry) -> + case maps:find(PairHash, Registry) of + error -> + maps:put(PairHash, K, Registry); + {ok, #key{pubhash = none, keyhash = none}} -> + maps:put(PairHash, K, Registry); + {ok, OldK = #key{keyhash = none}} -> + maps:put(PairHash, OldK#key{keyhash = KeyHash, key = Key}, Registry); + {ok, #key{keyhash = KeyHash}} -> + Registry + end; +update_registry(PairHash, + K = #key{keyhash = none, pubhash = PubHash, pub = Pub}, + Registry) -> + case maps:find(PairHash, Registry) of + error -> + maps:put(PairHash, K, Registry); + {ok, #key{pubhash = none, keyhash = none}} -> + maps:put(PairHash, K, Registry); + {ok, OldK = #key{pubhash = none}} -> + maps:put(PairHash, OldK#key{pubhash = PubHash, pub = Pub}, Registry); + {ok, #key{pubhash = PubHash}} -> + Registry + end; +update_registry(PairHash, K, Registry) -> + maps:put(PairHash, K, Registry). + +stash_keys(KX) -> + Bin = term_to_binary(KX), + file:write_file(key_stash_path(), Bin). + + +-spec do_get_key(Type, KeyID, RK) -> Result + when Type :: public | private, + KeyID :: zx:key_id(), + RK :: rk_index(), + Result :: {ok, public_key:rsa_public_key() | public_key:rsa_private_key()} + | {error, Reason}, + Reason :: bad_realm + | no_pub + | no_key + | bad_key. + +do_get_key(Type, {Realm, KeyName}, RK) -> + case maps:find(Realm, RK) of + {ok, Registry} -> do_get_key2(Type, KeyName, Registry); + error -> {error, bad_realm} + end. + +do_get_key2(public, KeyName, Registry) -> + case maps:find(KeyName, Registry) of + {ok, #key{pub = none}} -> {error, no_pub}; + {ok, #key{pub = Pub}} -> {ok, Pub}; + error -> {error, bad_key} + end; +do_get_key2(private, KeyName, Registry) -> + case maps:find(KeyName, Registry) of + {ok, #key{key = none}} -> {error, no_key}; + {ok, #key{key = Key}} -> {ok, Key}; + error -> {error, bad_key} + end. + + +-spec do_keybin(Type, KeyID, RK) -> Result + when Type :: public | private, + KeyID :: zx:key_id(), + RK :: rk_index(), + Result :: {ok, binary()} + | {error, bad_realm | bad_key | no_key | file:posix()}. + +do_keybin(Type, KeyID = {Realm, _}, RK) -> + case maps:find(Realm, RK) of + {ok, Registry} -> do_keybin2(Type, KeyID, Registry); + error -> {error, bad_realm} + end. + +do_keybin2(public, KeyID = {_, KeyName}, Registry) -> + case maps:find(KeyName, Registry) of + {ok, #key{pubhash = none}} -> {error, no_key}; + {ok, #key{}} -> file:read_file(zx_key:path(public, KeyID)); + error -> {error, bad_key} + end; +do_keybin2(private, KeyID, Registry) -> + KeyName = element(2, KeyID), + case maps:find(KeyName, Registry) of + {ok, #key{keyhash = none}} -> {error, no_key}; + {ok, #key{}} -> file:read_file(zx_key:path(private, KeyID)); + error -> {error, bad_key} + end. + + +-spec do_have_key(Type, KeyID, RK) -> boolean() + when Type :: public | private, + KeyID :: zx:key_id(), + RK :: rk_index(). + +do_have_key(Type, {Realm, KeyName}, RK) -> + case maps:find(Realm, RK) of + {ok, Registry} -> + case maps:find(KeyName, Registry) of + {ok, K} -> do_have_key2(Type, K); + error -> false + end; + error -> + false + end. + +do_have_key2(public, #key{pub = none}) -> false; +do_have_key2(private, #key{key = none}) -> false; +do_have_key2(_, #key{}) -> true. + + +-spec do_list_keys(Type, Owner, KX) -> Result + when Type :: public | private, + Owner :: zx:realm() | zx:user_id(), + KX :: key_index(), + Result :: {ok, [zx:key_hash()]} + | {error, bad_realm | bad_user}. + +do_list_keys(public, UserID, {_, _, UK}) when is_tuple(UserID) -> + case maps:find(UserID, UK) of + {ok, {Keys, _}} -> {ok, Keys}; + error -> {error, bad_user} + end; +do_list_keys(private, UserID, {_, _, UK}) when is_tuple(UserID) -> + case maps:find(UserID, UK) of + {ok, {_, Keys}} -> {ok, Keys}; + error -> {error, bad_user} + end; +do_list_keys(public, Realm, {RK, _, _}) -> + case maps:find(Realm, RK) of + {ok, Registry} -> + List = maps:to_list(Registry), + {ok, [ID || {ID, #key{pubhash = PH}} <- List, is_binary(PH)]}; + error -> + {error, bad_realm} + end; +do_list_keys(private, Realm, {RK, _, _}) -> + case maps:find(Realm, RK) of + {ok, Registry} -> + List = maps:to_list(Registry), + {ok, [ID || {ID, #key{keyhash = KH}} <- List, is_binary(KH)]}; + error -> + {error, bad_realm} + end. + + +-spec do_drop_realm(Realm, State) -> {ok, NewState} + when Realm :: zx:realm(), + State :: state(), + NewState :: state(). + +do_drop_realm(Realm, State = #s{kx = {RealmIndex, Pairs}}) -> + NewKX = + case maps:take(Realm, RealmIndex) of + {KeyIndex, NextRealmIndex} -> + NewPairs = scrub_pairs(maps:values(KeyIndex), Pairs), + {NextRealmIndex, NewPairs}; + error -> + {RealmIndex, Pairs} + end, + Dirs = [etc, var, tmp, log, key, zsp, lib], + RM = fun(D) -> ok = zx_lib:rm_rf(zx_lib:path(D, Realm)) end, + ok = lists:foreach(RM, Dirs), + NewState = do_abdicate(Realm, State), + {ok, NewState#s{kx = NewKX}}. + +scrub_pairs([#key{pubhash = PubHash, keyhash = KeyHash} | Rest], Pairs) -> + scrub_pairs(Rest, maps:without([PubHash, KeyHash], Pairs)); +scrub_pairs([], Pairs) -> + Pairs. + + +-spec become_proxy(State) -> NewState + when State :: state(), + NewState :: state(). + +become_proxy(State = #s{cx = zomp}) -> + ok = log(warning, "Already set as zomp node."), + State; +become_proxy(State = #s{cx = proxy}) -> + {ok, Port} = zx_peer_man:listen(), + ok = write_lockfile(Port), + {ok, ID} = zx_proxy:youre_fired(), + State#s{id = ID, cx = zomp}; +become_proxy(State) -> + ok = log(warning, "Already acting proxy."), + State. + + +-spec become_zomp_node(State) -> NewState + when State :: state(), + NewState :: state(). + +become_zomp_node(State = #s{cx = zomp}) -> + ok = log(warning, "Already set as zomp node."), + State; +become_zomp_node(State = #s{cx = proxy}) -> + NewState = become_proxy(State), + NewState#s{cx = zomp}; +become_zomp_node(State) -> + NewState = wipe_connections(State), + NewState#s{cx = zomp}. + + %%% Monitor Index ADT Interface Functions -spec mx_new() -> monitor_index(). @@ -1350,15 +2168,21 @@ mx_upgrade_conn(Pid, MX) -> %% exists. Returns a tuple including the remaining request references in the case of %% a conn type. -mx_del_monitor(Pid, attempt, MX) -> +mx_del_monitor(Pid, Category, MX) -> + case maps:is_key(Pid, MX) of + true -> mx_del_monitor2(Pid, Category, MX); + false -> MX + end. + +mx_del_monitor2(Pid, attempt, MX) -> {{Ref, attempt}, NewMX} = maps:take(Pid, MX), true = demonitor(Ref, [flush]), NewMX; -mx_del_monitor(Pid, conn, MX) -> +mx_del_monitor2(Pid, conn, MX) -> {{Ref, conn}, NewMX} = maps:take(Pid, MX), true = demonitor(Ref, [flush]), NewMX; -mx_del_monitor(Pid, {requestor, Req}, MX) -> +mx_del_monitor2(Pid, {requestor, Req}, MX) -> case maps:take(Pid, MX) of {{Ref, {[Req], []}}, NextMX} -> true = demonitor(Ref, [flush]), @@ -1367,7 +2191,7 @@ mx_del_monitor(Pid, {requestor, Req}, MX) -> NewReqs = lists:delete(Req, Reqs), maps:put(Pid, {Ref, {NewReqs, Subs}}, NextMX) end; -mx_del_monitor(Pid, {subscriber, Sub}, MX) -> +mx_del_monitor2(Pid, {subscriber, Sub}, MX) -> case maps:take(Pid, MX) of {{Ref, {[], [Sub]}}, NextMX} -> true = demonitor(Ref, [flush]), @@ -1378,7 +2202,7 @@ mx_del_monitor(Pid, {subscriber, Sub}, MX) -> end. --spec mx_crashed_monitor(Pid, MX) -> Result +-spec mx_drop_monitor(Pid, MX) -> Result when Pid :: pid(), MX :: monitor_index(), Result :: {Type, NewMX} @@ -1388,7 +2212,7 @@ mx_del_monitor(Pid, {subscriber, Sub}, MX) -> | {Reqs :: [id()], Subs :: [tuple()]}, NewMX :: monitor_index(). -mx_crashed_monitor(Pid, MX) -> +mx_drop_monitor(Pid, MX) -> case maps:take(Pid, MX) of {{Ref, Type}, NewMX} -> true = demonitor(Ref, [flush]), @@ -1409,26 +2233,32 @@ mx_crashed_monitor(Pid, MX) -> %%% %%% Return values often carry some status information with them. --spec cx_load() -> conn_index(). +-spec new_cx() -> conn_index(). + +new_cx() -> #cx{}. + + +-spec cx_load(state()) -> state(). %% @private %% Used to load a connection index populated with necessary realm configuration data %% and cached mirror data, if such things can be found in the system, otherwise return %% a blank connection index structure. -cx_load() -> - case cx_populate() of - {ok, Realms} -> - #cx{realms = maps:from_list(Realms)}; +cx_load(State = #s{conf = Conf}) -> + case cx_populate(Conf) of + {ok, CX} -> + State#s{cx = CX}; {error, Reason} -> Message = "Realm data and host cache load failed with : ~160tp", ok = log(error, Message, [Reason]), ok = log(warning, "No realms configured."), - #cx{} + State#s{cx = #cx{}} end. --spec cx_populate() -> Result - when Result :: {ok, conn_index()} +-spec cx_populate(Conf) -> Result + when Conf :: conf(), + Result :: {ok, conn_index()} | {error, Reason}, Reason :: no_realms | file:posix(). @@ -1439,235 +2269,163 @@ cx_load() -> %% home could be selected by an installer (especially on consumer systems like Windows %% where any number of wild things might be going on in the user's filesystem). -cx_populate() -> - Realms = zx_lib:list_realms(), - CX = lists:foldl(fun cx_populate/2, [], Realms), - {ok, CX}. +cx_populate(#conf{realms = Realms, managed = Managed, mirrors = Mirrors}) -> + Populate = cx_populator(sets:to_list(Managed)), + MirrorList = queue:from_list(sets:to_list(Mirrors)), + {Records, Canonical} = lists:foldl(Populate, {[], MirrorList}, Realms), + Hosts = cx_load_hosts_cache(), + {ok, #cx{realms = maps:from_list(Records), mirrors = Canonical, hosts = Hosts}}. --spec cx_populate(Realms, CX) -> NewCX - when Realms :: [zx:realm()], - CX :: [{zx:realm(), realm_meta()}], - NewCX :: [{zx:realm(), realm_meta()}]. +-spec cx_populator(Managed) -> fun((Realm, CX) -> NewCX) + when Managed :: [zx:realm()], + Realm :: zx:realm(), + CX :: [{zx:realm(), realm_meta()}], + NewCX :: [{zx:realm(), realm_meta()}]. %% @private %% Pack an initially empty conn_index() with realm meta and host cache data. %% Should not halt on a corrupted, missing, malformed, etc. realm file but will log %% any loading errors. -cx_populate(Realm, CX) -> - case zx_lib:load_realm_conf(Realm) of - {ok, Meta} -> - Record = cx_load_realm_meta(Meta), - [{Realm, Record} | CX]; - {error, Reason} -> - Message = "Loading realm ~160tp failed with: ~160tp. Skipping...", - ok = log(warning, Message, [Realm, Reason]), - CX +cx_populator(Managed) -> + fun(Realm, {Records, Hosts}) -> + case {lists:member(Realm, Managed), zx_lib:load_realm_conf(Realm)} of + {true, {ok, Meta}} -> + Record = cx_load_realm_meta(Meta), + {[{Realm, Record#rmeta{assigned = managed}} | Records], Hosts}; + {false, {ok, Meta}} -> + Record = #rmeta{prime = Prime} = cx_load_realm_meta(Meta), + {[{Realm, Record} | Records], queue:in(Prime, Hosts)}; + {false, {error, Reason}} -> + Message = "Loading realm ~160tp failed with: ~160tp. Skipping...", + ok = log(warning, Message, [Realm, Reason]), + {Records, Hosts} + end end. -spec cx_load_realm_meta(Meta) -> Result - when Meta :: [{Key :: atom(), Value :: term()}], - Result :: {zx:realm(), realm_meta()}. + when Meta :: [{Key :: atom(), Value :: term()}], + Result :: {zx:realm(), realm_meta()}. %% @private %% This function MUST adhere to the realmfile definition found at. cx_load_realm_meta(Meta) -> Realm = maps:get(realm, Meta), - Retries = zx_sys_conf:retries(get(zx_sys_conf)), Basic = - #rmeta{retries = Retries, - prime = maps:get(prime, Meta), - sysop = maps:get(sysop, Meta), - key = maps:get(key, Meta)}, - cx_load_cache(Realm, Basic). + #rmeta{prime = maps:get(prime, Meta), + sysop = maps:get(sysop, Meta), + key = maps:get(key, Meta)}, + cx_load_realm_cache(Realm, Basic). --spec cx_load_cache(Realm, Basic) -> Complete +-spec cx_load_realm_cache(Realm, Basic) -> Complete when Realm :: zx:realm(), Basic :: realm_meta(), Complete :: realm_meta(). -%% @private -%% Receive a realm_meta() that lacks any cache data and load the realm's cache file -%% if it exists, and return it fully populated. -%% FIXME: If several instances of zomp or zx are running at once it may be possible -%% to run into file access contention or receive an incomplete read on some -%% systems. At the moment this is only a remote possibility and for now should -%% be handled by simply re-running whatever command caused the failure. -%% Better file contention and parallel-executing system handling should -%% eventually be implemented, especially if zx becomes common for end-user -%% GUI programs. -%% NOTE: This "fixme" will only apply until the zx universal lock is implemented. -cx_load_cache(Realm, Basic) -> - CacheFile = cx_cache_file(Realm), +cx_load_realm_cache(Realm, Basic) -> + CacheFile = cx_realm_cache(Realm), case file:consult(CacheFile) of {ok, Cache} -> - {serial, Serial} = lists:keyfind(serial, 1, Cache), - {private, Private} = lists:keyfind(private, 1, Cache), - {mirrors, Mirrors} = lists:keyfind(mirrors, 1, Cache), - PQueue = queue:from_list(Private), - Enqueue = fun(H, Q) -> queue:in(H, Q) end, - MQueue = lists:foldl(Enqueue, Mirrors, PQueue), - Basic#rmeta{serial = Serial, private = Private, mirrors = MQueue}; + Serial = proplists:get_value(serial, Cache), + Basic#rmeta{serial = Serial}; {error, enoent} -> Basic end. +cx_load_hosts_cache() -> + case file:consult(cx_hosts_cache()) of + {ok, Hosts} -> queue:from_list(Hosts); + {error, enoent} -> queue:new() + end. + + -spec cx_store_cache(CX) -> Result when CX :: conn_index(), Result :: ok | {error, file:posix()}. -cx_store_cache(#cx{realms = Realms}) -> - lists:foreach(fun cx_write_cache/1, maps:to_list(Realms)). - +cx_store_cache(#cx{realms = Realms, hosts = Hosts}) -> + ok = lists:foreach(fun cx_write_cache/1, maps:to_list(Realms)), + zx_lib:write_terms(cx_hosts_cache(), queue:to_list(Hosts)). + -spec cx_write_cache({zx:realm(), realm_meta()}) -> ok. -%% @private -%% FIXME: The same concerns as noted in the cx_load_cache/2 FIXME comment apply here. -%% NOTE: This "fixme" will only apply until the zx universal lock is implemented. -cx_write_cache({Realm, - #rmeta{serial = Serial, private = Private, mirrors = Mirrors}}) -> - CacheFile = cx_cache_file(Realm), - MList = queue:to_list(Mirrors), - ActualMirrors = lists:subtract(MList, Private), - CacheMeta = [{serial, Serial}, {mirrors, ActualMirrors}], - ok = zx_lib:write_terms(CacheFile, CacheMeta), - log(info, "Wrote cache for realm ~ts", [Realm]). +cx_write_cache({Realm, #rmeta{serial = Serial}}) -> + CacheFile = cx_realm_cache(Realm), + CacheMeta = [{serial, Serial}], + zx_lib:write_terms(CacheFile, CacheMeta). --spec cx_cache_file(zx:realm()) -> file:filename(). +-spec cx_realm_cache(zx:realm()) -> file:filename(). -cx_cache_file(Realm) -> +cx_realm_cache(Realm) -> filename:join(zx_lib:path(var, Realm), "realm.cache"). --spec cx_realms(conn_index()) -> [zx:realms()]. +cx_hosts_cache() -> + filename:join(zx_lib:path(var), "hosts.cache"). + + +-spec cx_realms(conn_index()) -> [zx:realm()]. cx_realms(#cx{realms = Realms}) -> - maps:keys(Realms). + maps:keys(Realms); +cx_realms(zomp) -> + zx_lib:list_realms(); +cx_realms(proxy) -> + {ok, Realms} = zx_proxy:request(list), + Realms. --spec cx_next_host(Realm, CX) -> Result - when Realm :: zx:realm(), +-spec cx_mirrors(CX) -> Result + when CX :: conn_index(), + Result :: {ok, [zx:host()]} + | {error, zomp}. + +cx_mirrors(#cx{mirrors = Mirrors}) -> + {ok, queue:to_list(Mirrors)}; +cx_mirrors(zomp) -> + {error, zomp}. + + +-spec cx_check_service(Realms, CX) -> Result + when Realms :: [zx:realm()], CX :: conn_index(), - Result :: {ok, Next, NewCX} - | {prime, Prime, NewCX} - | {error, Reason, NewCX}, - Next :: zx:host(), - Prime :: zx:host(), + Result :: {ok, NewCX} + | {connect, Host, NewCX}, NewCX :: conn_index(), - Reason :: bad_realm - | connected. -%% @private -%% Given a realm, retun the next cached host location to which to connect. Returns -%% error if the realm is already assigned, if it is available but should have been -%% assigned, or if the realm is not configured. -%% If all cached mirrors are exhausted it will return the realm's prime host and -%% reload the mirrors queue with private mirrors. + Host :: zx:host(). -cx_next_host(Realm, CX = #cx{realms = Realms}) -> - case maps:find(Realm, Realms) of - {ok, Meta = #rmeta{assigned = none, available = [Pid | Pids]}} -> - ok = log(warning, "Call to cx_next_host/2 when connection available."), - NewMeta = Meta#rmeta{assigned = Pid, available = Pids}, - NewRealms = maps:put(Realm, NewMeta, Realms), - NewCX = CX#cx{realms = NewRealms}, - {error, connected, NewCX}; - {ok, Meta = #rmeta{assigned = none, available = []}} -> - {Outcome, Host, NewMeta} = cx_next_host(Meta), - NewRealms = maps:put(Realm, NewMeta, Realms), - NewCX = CX#cx{realms = NewRealms}, - {Outcome, Host, NewCX}; - {ok, #rmeta{assigned = Conn}} when is_pid(Conn) -> - ok = log(warning, "Call to cx_next_host/2 when connection assigned."), - {error, connected, CX}; - error -> - {error, bad_realm, CX} +cx_check_service([], CX) -> + {ok, CX}; +cx_check_service([Realm | Rest], CX = #cx{realms = Realms}) -> + case maps:get(Realm, Realms) of + #rmeta{assigned = none, available = []} -> + {Host, NewCX} = cx_next_host(CX), + {connect, Host, NewCX}; + R = #rmeta{assigned = none, available = [Conn | Conns]} -> + NewR = R#rmeta{assigned = Conn, available = Conns}, + NewRealms = maps:put(Realm, NewR, Realms), + cx_check_service(Rest, CX#cx{realms = NewRealms}); + #rmeta{} -> + cx_check_service(Rest, CX) end. --spec cx_next_host(Meta) -> Result - when Meta :: realm_meta(), - Result :: {ok, Next, NewMeta} - | {prime, Prime, NewMeta}, - Next :: zx:host(), - Prime :: zx:host(), - NewMeta :: realm_meta(). - -cx_next_host(Meta = #rmeta{prime = Prime, private = Private, mirrors = Mirrors}) -> - case queue:out(Mirrors) of - {{value, Next}, NewMirrors} -> - {ok, Next, Meta#rmeta{mirrors = NewMirrors}}; - {empty, Mirrors} -> - Enqueue = fun(H, Q) -> queue:in(H, Q) end, - NewMirrors = lists:foldl(Enqueue, Mirrors, Private), - {prime, Prime, Meta#rmeta{mirrors = NewMirrors}} - end. - - --spec cx_next_hosts(N, Realm, CX) -> Result - when N :: non_neg_integer(), - Realm :: zx:realm(), - CX :: conn_index(), - Result :: {ok, Hosts, NewCX} - | {error, Reason}, - Hosts :: [zx:host()], - NewCX :: conn_index(), - Reason :: {connected, Conn :: pid()} - | bad_realm. -%% @private -%% This function allows recruiting an arbitrary number of hosts from the host cache -%% of a given realm, taking private mirrors first, then public mirrors, and ending -%% with the prime node for the realm if no others exist. - -cx_next_hosts(N, Realm, CX) -> - cx_next_hosts(N, Realm, [], CX). - - -cx_next_hosts(N, Realm, Hosts, CX) when N > 0 -> - case cx_next_host(Realm, CX) of - {ok, Host, NewCX} -> cx_next_hosts(N - 1, Realm, [Host | Hosts], NewCX); - {prime, Host, NewCX} -> {ok, [Host | Hosts], NewCX}; - Error -> Error - end; -cx_next_hosts(0, _, Hosts, CX) -> - {ok, Hosts, CX}. - - --spec cx_maybe_add_attempt(Host, Realm, CX) -> Result - when Host :: zx:host(), - Realm :: zx:realm(), - CX :: conn_index(), - Result :: not_connected - | {ok, NewCX}, - NewCX :: conn_index(). - -cx_maybe_add_attempt(Host, Realm, CX = #cx{attempts = Attempts}) -> - case lists:keytake(Host, 2, Attempts) of +cx_next_host(CX = #cx{mirrors = Mirrors, hosts = Hosts}) -> + case queue:is_empty(Hosts) of + true -> + {wait, CX#cx{hosts = Mirrors}}; false -> - not_connected; - {value, {Pid, Host, Realms}, NextAttempts} -> - NewAttempts = [{Pid, Host, [Realm | Realms]} | NextAttempts], - NewCX = CX#cx{attempts = NewAttempts}, - {ok, NewCX} + {{value, Host}, NewHosts} = queue:out(Hosts), + {Host, CX#cx{hosts = NewHosts}} end. --spec cx_add_attempt(Pid, Host, Realm, CX) -> NewCX - when Pid :: pid(), - Host :: zx:host(), - Realm :: zx:realm(), - CX :: conn_index(), - NewCX :: conn_index(). - -cx_add_attempt(Pid, Host, Realm, CX = #cx{attempts = Attempts}) -> - CX#cx{attempts = [{Pid, Host, [Realm]} | Attempts]}. - - -spec cx_connected(Available, Pid, CX) -> Result when Available :: [{zx:realm(), zx:serial()}], Pid :: pid(), @@ -1675,21 +2433,11 @@ cx_add_attempt(Pid, Host, Realm, CX = #cx{attempts = Attempts}) -> Result :: {Assignment, NewCX}, Assignment :: assigned | unassigned, NewCX :: conn_index(). -%% @private -%% An abstract data handler which is called whenever a new connection is successfully -%% established by a zx_conn. Any unconnected realms with a valid serial will be -%% assigned to the new connection; if none are needed then the connection is closed. -%% The successful host is placed back in the hosts queue for each available realm. -%% The return value is a tuple that indicates whether the new connection was assigned -%% or not and the updated CX data value. -cx_connected(Available, Pid, CX = #cx{attempts = Attempts}) -> - {value, Attempt, NewAttempts} = lists:keytake(Pid, 1, Attempts), - Host = element(2, Attempt), +cx_connected(Available, Pid, CX) -> Realms = [element(1, R) || R <- Available], - NewCX = CX#cx{attempts = NewAttempts}, - Conn = #conn{pid = Pid, host = Host, realms = Realms}, - cx_connected(unassigned, Available, Conn, NewCX). + Conn = #conn{pid = Pid, realms = Realms}, + cx_connected(unassigned, Available, Conn, CX). -spec cx_connected(A, Available, Conn, CX) -> {NewA, NewCX} @@ -1699,9 +2447,6 @@ cx_connected(Available, Pid, CX = #cx{attempts = Attempts}) -> CX :: conn_index(), NewA :: unassigned | assigned, NewCX :: conn_index(). -%% @private -%% Only accept a new realm as available if the reported serial is equal or newer to the -%% highest known serial. cx_connected(A, [{Realm, Serial} | Rest], @@ -1717,10 +2462,7 @@ cx_connected(A, error -> cx_connected(A, Rest, Conn, CX) end; -cx_connected(A, - [], - Conn, - CX = #cx{conns = Conns}) -> +cx_connected(A, [], Conn, CX = #cx{conns = Conns}) -> {A, CX#cx{conns = [Conn | Conns]}}. @@ -1740,173 +2482,71 @@ cx_connected(A, cx_connected(_, Realm, - #conn{pid = Pid, host = Prime}, - Meta = #rmeta{prime = Prime, assigned = none}, + #conn{pid = Pid}, + Meta = #rmeta{assigned = none}, CX = #cx{realms = Realms}) -> NewMeta = Meta#rmeta{assigned = Pid}, NewRealms = maps:put(Realm, NewMeta, Realms), NewCX = CX#cx{realms = NewRealms}, {assigned, NewCX}; -cx_connected(_, - Realm, - #conn{pid = Pid, host = Host}, - Meta = #rmeta{mirrors = Mirrors, assigned = none}, - CX = #cx{realms = Realms}) -> - NewMirrors = cx_enqueue_unique(Host, Mirrors), - NewMeta = Meta#rmeta{mirrors = NewMirrors, assigned = Pid}, - NewRealms = maps:put(Realm, NewMeta, Realms), - NewCX = CX#cx{realms = NewRealms}, - {assigned, NewCX}; -cx_connected(A, - _, - #conn{host = Prime}, - #rmeta{prime = Prime}, - CX) -> - {A, CX}; -cx_connected(A, - Realm, - #conn{host = Host}, - Meta = #rmeta{mirrors = Mirrors}, - CX = #cx{realms = Realms}) -> - NewMirrors = cx_enqueue_unique(Host, Mirrors), - NewMeta = Meta#rmeta{mirrors = NewMirrors}, - NewRealms = maps:put(Realm, NewMeta, Realms), - NewCX = CX#cx{realms = NewRealms}, - {A, NewCX}. +cx_connected(A, _, _, _, CX) -> + {A, CX}. --spec cx_enqueue_unique(term(), queue:queue()) -> queue:queue(). -%% @private -%% Simple function to ensure that only unique elements are added to a queue. Obviously -%% this operation is extremely general and O(n) in complexity due to the use of -%% queue:member/2. +-spec cx_redirect(Targets, CX) -> NewCX + when Targets :: [{zx:host(), [zx:realm()]}], + CX :: conn_index(), + NewCX :: conn_index(). -cx_enqueue_unique(Element, Queue) -> - case queue:member(Element, Queue) of - true -> Queue; - false -> queue:in(Element, Queue) - end. - - --spec cx_failed(Conn, CX) -> {Realms, NewCX} - when Conn :: pid(), - CX :: conn_index(), - Realms :: [zx:realm()], - NewCX :: conn_index(). -%% @private -%% Remove a failed attempt and all its associations. - -cx_failed(Conn, CX = #cx{attempts = Attempts}) -> - {value, Attempt, NewAttempts} = lists:keytake(Conn, 1, Attempts), - Realms = element(3, Attempt), - {Realms, CX#cx{attempts = NewAttempts}}. - - --spec cx_redirect(Conn, Hosts, CX) -> {Unassigned, NewCX} - when Conn :: pid(), - Hosts :: [{zx:host(), [zx:realm()]}], - CX :: conn_index(), - Unassigned :: [zx:realm()], - NewCX :: conn_index(). -%% @private -%% Remove a redirected connection attempt from CX, add its redirect hosts to the -%% mirror queue, and proceed to make further connection atempts to all unassigned -%% realms. This can cause an inflationary number of new connection attempts, but this -%% is considered preferrable because the more mirrors fail the longer the user is -%% waiting and the more urgent the need to discover a working node becomes. - -cx_redirect(Conn, Hosts, CX = #cx{attempts = Attempts}) -> - NewAttempts = lists:keydelete(Conn, 1, Attempts), - NextCX = CX#cx{attempts = NewAttempts}, - NewCX = cx_redirect(Hosts, NextCX), - Unassigned = cx_unassigned(NewCX), - {Unassigned, NewCX}. - - --spec cx_redirect(Hosts, CX) -> NewCX - when Hosts :: [{zx:host(), [zx:realm()]}], - CX :: conn_index(), - NewCX :: conn_index(). -%% @private -%% Add the host to any realm mirror queues that it provides. - -cx_redirect([{Host, Provided} | Rest], CX = #cx{realms = Realms}) -> - Apply = - fun(R, Rs) -> - case maps:find(R, Rs) of - {ok, Meta = #rmeta{mirrors = Mirrors}} -> - NewMirrors = cx_enqueue_unique(Host, Mirrors), - NewMeta = Meta#rmeta{mirrors = NewMirrors}, - maps:put(R, NewMeta, Rs); - error -> - Rs - end - end, - NewRealms = lists:foldl(Apply, Realms, Provided), - cx_redirect(Rest, CX#cx{realms = NewRealms}); +cx_redirect([{Host, Provided} | Rest], CX = #cx{hosts = Hosts}) -> + NewHosts = zx_lib:enqueue_unique(Host, Hosts), + HostString = zx_net:host_string(Host), + ok = log(info, "Host ~ts provides: ~tp", [HostString, Provided]), + cx_redirect(Rest, CX#cx{hosts = NewHosts}); cx_redirect([], CX) -> CX. --spec cx_unassigned(CX) -> Unassigned - when CX :: conn_index(), - Unassigned :: [zx:realm()]. -%% @private -%% Scan CX#cx.realms for unassigned realms and return a list of all unassigned -%% realm names. +-spec cx_wipe(CX) -> ok + when CX :: conn_index(). -cx_unassigned(#cx{realms = Realms}) -> - NotAssigned = - fun(Realm, #rmeta{assigned = Conn}, Unassigned) -> - case Conn == none of - true -> [Realm | Unassigned]; - false -> Unassigned - end - end, - maps:fold(NotAssigned, [], Realms). +cx_wipe(CX = #cx{conns = Conns, mirrors = Mirrors}) -> + Pids = [P || #conn{pid = P} <- Conns], + ok = lists:foreach(fun zx_conn:retire/1, Pids), + NewCX = CX#cx{conns = [], hosts = Mirrors}, + {Pids, NewCX}. --spec cx_disconnected(Conn, CX) -> {Requests, Subs, Unassigned, NewCX} - when Conn :: pid(), - CX :: conn_index(), - Requests :: [id()], - Subs :: [zx:package()], - Unassigned :: [zx:realm()], - NewCX :: conn_index(). -%% @private -%% An abstract data handler which is called whenever a connection terminates. -%% This function removes all data related to the disconnected pid and its assigned -%% realms, and returns the monitor reference of the pid, a list of realms that are now -%% unassigned, and an updated connection index. +-spec cx_disconnected(Conn, CX) -> {Requests, Subs, NewCX} + when Conn :: pid(), + CX :: conn_index(), + Requests :: [id()], + Subs :: [zx:package()], + NewCX :: conn_index(). cx_disconnected(Pid, CX = #cx{realms = Realms, conns = Conns}) -> {value, Conn, NewConns} = lists:keytake(Pid, #conn.pid, Conns), - #conn{host = Host, requests = Requests, subs = Subs} = Conn, - NewRealms = cx_scrub_assigned(Pid, Host, Realms), + #conn{requests = Requests, subs = Subs} = Conn, + NewRealms = cx_scrub_assigned(Pid, Realms), NewCX = CX#cx{realms = NewRealms, conns = NewConns}, - Unassigned = cx_unassigned(NewCX), - {Requests, Subs, Unassigned, NewCX}. + {Requests, Subs, NewCX}. --spec cx_scrub_assigned(Pid, Host, Realms) -> NewRealms - when Pid :: pid(), - Host :: zx:host(), - Realms :: [realm_meta()], - NewRealms :: [realm_meta()]. +-spec cx_scrub_assigned(Pid, Realms) -> NewRealms + when Pid :: pid(), + Realms :: [realm_meta()], + NewRealms :: [realm_meta()]. %% @private %% This could have been performed as a set of two list operations (a partition and a %% map), but to make the procedure perfectly clear it is written out explicitly. -cx_scrub_assigned(Pid, Host, Realms) -> +cx_scrub_assigned(Pid, Realms) -> Scrub = fun - (_, V = #rmeta{mirrors = M, available = A, assigned = C}) when C == Pid -> - V#rmeta{mirrors = cx_enqueue_unique(Host, M), - available = lists:delete(Pid, A), - assigned = none}; - (_, V = #rmeta{mirrors = M, available = A}) -> - V#rmeta{mirrors = cx_enqueue_unique(Host, M), - available = lists:delete(Pid, A)} + (_, V = #rmeta{available = A, assigned = C}) when C == Pid -> + V#rmeta{available = lists:delete(Pid, A), assigned = none}; + (_, V = #rmeta{available = A}) -> + V#rmeta{available = lists:delete(Pid, A)} end, maps:map(Scrub, Realms). @@ -1923,9 +2563,10 @@ cx_scrub_assigned(Pid, Host, Realms) -> cx_resolve(Realm, #cx{realms = Realms}) -> case maps:find(Realm, Realms) of - {ok, #rmeta{assigned = none}} -> unassigned; - {ok, #rmeta{assigned = Conn}} -> {ok, Conn}; - error -> unconfigured + {ok, #rmeta{assigned = none}} -> unassigned; + {ok, #rmeta{assigned = managed}} -> managed; + {ok, #rmeta{assigned = Conn}} -> {ok, Conn}; + error -> unconfigured end. @@ -1950,8 +2591,8 @@ cx_pre_send(Realm, ID, CX = #cx{conns = Conns}) -> NewConn = Conn#conn{requests = NewRequests}, NewCX = CX#cx{conns = [NewConn | NextConns]}, {ok, Pid, NewCX}; - NoGo -> - NoGo + Other -> + Other end. @@ -2098,4 +2739,6 @@ cx_clear_client(Pid, DeadReqs, DeadSubs, CX = #cx{conns = Conns}) -> C#conn{requests = NewRequests, subs = NewSubs} end, NewConns = lists:map(Clear, Conns), - CX#cx{conns = NewConns}. + CX#cx{conns = NewConns}; +cx_clear_client(_, _, _, Status) -> + Status. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_key.erl similarity index 54% rename from zomp/lib/otpr/zx/0.1.0/src/zx_key.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_key.erl index 05c6ba8..2e9848d 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_key.erl @@ -8,118 +8,53 @@ %%% @end -module(zx_key). +-vsn("0.2.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). --export([ensure_keypair/1, have_key/2, path/2, - prompt_keygen/1, generate_rsa/1, - load/2, verify/3]). +-export([path/2, + generate_rsa/1, + load/2, sign/2, verify/3]). -include("zx_logger.hrl"). %%% Functions --spec ensure_keypair(zx:key_id()) -> zx:outcome(). -%% @private -%% Check if both the public and private key based on KeyID exists. +-spec path(public | private, zx:key_id()) -> file:filename(). -ensure_keypair(KeyID = {Realm, KeyName}) -> - case {have_key(public, KeyID), have_key(private, KeyID)} of - {true, true} -> - true; - {false, true} -> - Format = "Public key ~ts/~ts cannot be found", - Message = io_lib:format(Format, [Realm, KeyName]), - {error, Message, 2}; - {true, false} -> - Format = "Private key ~ts/~ts cannot be found", - Message = io_lib:format(Format, [Realm, KeyName]), - {error, Message, 2}; - {false, false} -> - Format = "Key pair ~ts/~ts cannot be found", - Message = io_lib:format(Format, [Realm, KeyName]), - {error, Message, 2} - end. - - --spec have_key(Type, KeyID) -> boolean() - when Type :: public | private, - KeyID :: zx:key_id(). -%% @private -%% Determine whether the indicated key is present. - -have_key(Type, KeyID) -> - filelib:is_regular(path(Type, KeyID)). - - --spec path(Type, KeyID) -> Path - when Type :: public | private, - KeyID :: zx:key_id(), - Path :: file:filename(). -%% @private -%% Given KeyID, return the path to the key type indicated. - -path(public, {Realm, KeyName}) -> - filename:join(zx_lib:path(key, Realm), KeyName ++ ".pub.der"); -path(private, {Realm, KeyName}) -> - filename:join(zx_lib:path(key, Realm), KeyName ++ ".key.der"). +path(Type, {Realm, KeyHash}) -> + Size = byte_size(KeyHash) * 8, + <> = KeyHash, + String = integer_to_list(N, 36), + Name = + case Type of + public -> String ++ ".pub.der"; + private -> String ++ ".key.der" + end, + zx_lib:path(key, Realm, Name). %%% Key generation --spec prompt_keygen(zx:user_id()) -> zx:key_name(). -%% @private -%% Prompt the user for a valid KeyPrefix to use for naming a new RSA keypair. -prompt_keygen(UserID = {Realm, UserName}) -> - Message = - "~nKEY NAME~n" - "Enter a name for your new key pair.~n" - "Valid names must start with a lower-case letter, and can include " - "only lower-case letters, numbers, and underscores, but no series of " - "consecutive underscores. (That is: [a-z0-9_])~n" - " Example: my_key~n", - ok = io:format(Message), - KeyTag = zx_tty:get_input(), - case zx_lib:valid_lower0_9(KeyTag) of - true -> - KeyName = UserName ++ "-" ++ KeyTag, - ok = zx_key:generate_rsa({Realm, KeyName}), - KeyName; - false -> - ok = io:format("Bad key name ~ts. Try again.~n", [KeyTag]), - prompt_keygen(UserID) - end. - - --spec generate_rsa(KeyID) -> Result - when KeyID :: zx:key_id(), - Result :: ok - | {error, Reason}, - Reason :: keygen_fail - | exists. +-spec generate_rsa(Owner) -> Result + when Owner :: zx:realm() | zx:user_id(), + Result :: {ok, zx:key_hash()} + | {error, keygen_fail}. %% @private %% Generate an RSA keypair and write them in der format to the current directory, using %% filenames derived from Prefix. %% NOTE: The current version of this command is likely to only work on a unix system. -generate_rsa(KeyID = {Realm, KeyName}) -> - BaseName = filename:join(zx_lib:path(key, Realm), KeyName), - Pattern = BaseName ++ ".*.der", - case filelib:wildcard(Pattern) of - [] -> generate_rsa(KeyID, BaseName); - _ -> {error, exists} - end. - - -generate_rsa(KeyID, BaseName) -> - PemFile = BaseName ++ ".pub.pem", - KeyFile = path(private, KeyID), - PubFile = path(public, KeyID), - ok = log(info, "Generating ~s and ~s. Please be patient...", [KeyFile, PubFile]), +generate_rsa(Owner) -> + {ok, TmpDir} = zx_lib:mktemp_dir({"otpr", "zx"}), + PemFile = filename:join(TmpDir, "zx-tmp.pem"), + PubFile = filename:join(TmpDir, "zx-tmp.pub.der"), + KeyFile = filename:join(TmpDir, "zx-tmp.key.der"), + ok = tell("Generating keys. Please be patient..."), case gen_p_key(KeyFile) of ok -> ok = der_to_pem(KeyFile, PemFile), @@ -128,17 +63,32 @@ generate_rsa(KeyID, BaseName) -> Pub = public_key:pem_entry_decode(PemData), PubDer = public_key:der_encode('RSAPublicKey', Pub), ok = file:write_file(PubFile, PubDer), - case check_key(KeyFile, PubFile) of - true -> - ok = file:delete(PemFile), - log(info, "~ts and ~ts agree", [KeyFile, PubFile]); - false -> - ok = lists:foreach(fun file:delete/1, [PemFile, KeyFile, PubFile]), - ok = log(error, "Something has gone wrong."), - {error, keygen_fail} - end; + generate_rsa2(Owner, PemFile, KeyFile, PubFile); {error, no_ssl} -> - ok = log(error, "OpenSSL not found."), + ok = tell(error, "OpenSSL not found."), + {error, keygen_fail} + end. + +generate_rsa2(Owner, PemFile, KeyFile, PubFile) -> + {ok, PubBin} = file:read_file(PubFile), + {ok, KeyBin} = file:read_file(KeyFile), + Pub = public_key:der_decode('RSAPublicKey', PubBin), + Key = public_key:der_decode('RSAPrivateKey', KeyBin), + TestMessage = <<"Some test data to sign.">>, + Signature = public_key:sign(TestMessage, sha512, Key), + case public_key:verify(TestMessage, sha512, Signature, Pub) of + true -> + ok = tell(info, "~ts and ~ts agree", [KeyFile, PubFile]), + PubHash = crypto:hash(sha512, PubBin), + KeyHash = crypto:hash(sha512, KeyBin), + PairHash = crypto:hash(sha512, <>), + KeyData = {PairHash, {none, PubBin}, {none, KeyBin}}, + ok = zx_daemon:register_key(Owner, KeyData), + ok = zx_lib:rm_rf(filename:dirname(KeyFile)), + {ok, PairHash}; + false -> + ok = lists:foreach(fun file:delete/1, [PemFile, KeyFile, PubFile]), + ok = tell(error, "Something has gone wrong."), {error, keygen_fail} end. @@ -196,23 +146,6 @@ der_to_pem(KeyFile, PemFile) -> end. --spec check_key(KeyFile, PubFile) -> Result - when KeyFile :: file:filename(), - PubFile :: file:filename(), - Result :: true | false. -%% @private -%% Compare two keys for pairedness. - -check_key(KeyFile, PubFile) -> - {ok, KeyBin} = file:read_file(KeyFile), - {ok, PubBin} = file:read_file(PubFile), - Key = public_key:der_decode('RSAPrivateKey', KeyBin), - Pub = public_key:der_decode('RSAPublicKey', PubBin), - TestMessage = <<"Some test data to sign.">>, - Signature = public_key:sign(TestMessage, sha512, Key), - public_key:verify(TestMessage, sha512, Signature, Pub). - - -spec openssl() -> Result when Result :: {ok, Executable} | {error, no_ssl}, @@ -230,11 +163,11 @@ openssl() -> end, case os:find_executable(OpenSSL) of false -> - ok = log(error, "OpenSSL could not be found in this system's PATH."), - ok = log(error, "Install OpenSSL and then retry."), + M = "OpenSSL not foud in PATH. Install OpenSSL or add to path and retry.", + ok = tell(error, M), {error, no_ssl}; Path -> - log(info, "OpenSSL executable found at: ~ts", [Path]), + ok = tell("OpenSSL executable found at: ~ts", [Path]), {ok, OpenSSL} end. @@ -260,6 +193,15 @@ load(Type, KeyID) -> end. +-spec sign(Data, Key) -> Signature + when Data :: binary(), + Key :: public_key:rsa_private_key(), + Signature :: boolean(). + +sign(Data, Key) -> + public_key:sign(Data, sha512, Key). + + -spec verify(Data, Signature, PubKey) -> boolean() when Data :: binary(), Signature :: binary(), diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_lib.erl similarity index 88% rename from zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_lib.erl index 438e71d..d7a8e30 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_lib.erl @@ -10,13 +10,15 @@ %%% @end -module(zx_lib). +-vsn("0.2.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). -export([zomp_dir/0, find_zomp_dir/0, path/1, path/2, path/3, path/4, ppath/2, - force_dir/1, mktemp_dir/1, + new_logpath/1, userconf/1, + force_dir/1, mktemp_dir/1, random_string/0, list_realms/0, realm_exists/1, get_prime/1, realm_meta/1, read_project_meta/0, read_project_meta/1, read_package_meta/1, @@ -26,10 +28,12 @@ string_to_version/1, version_to_string/1, package_id/1, package_string/1, zsp_name/1, zsp_path/1, + print_user/1, find_latest_compatible/2, installed/1, realm_conf/1, load_realm_conf/1, build/0, rm_rf/1, rm/1, + enqueue_unique/2, time_diff/2, elapsed_time/1, b_to_t/1, b_to_ts/1]). @@ -64,7 +68,7 @@ find_zomp_dir() -> case os:type() of {unix, _} -> Home = os:getenv("HOME"), - Dir = ".zomp", + Dir = "zomp", filename:join(Home, Dir); {win32, _} -> Home = os:getenv("LOCALAPPDATA"), @@ -113,11 +117,34 @@ path(Type, Realm, Name, Version) -> -spec ppath(core_dir(), zx:package_id()) -> file:filename(). %% @private -%% An alias for path/4, but more convenient when needing a path from a closed +%% An alias for path/3,4, but more convenient when needing a path from a closed %% package_id(). +ppath(Type, {Realm, Name, {z, z, z}}) -> + path(Type, Realm, Name); ppath(Type, {Realm, Name, Version}) -> - path(Type, Realm, Name, Version). + path(Type, Realm, Name, Version); +ppath(Type, {Realm, Name}) -> + path(Type, Realm, Name). + + +-spec new_logpath(zx:package_id()) -> file:filename(). + +new_logpath(PackageID = {Realm, Name, _}) -> + Dir = path(log, Realm, Name), + ok = force_dir(Dir), + {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(), + Format = "~4..0w~2..0w~2..0w_~2..0w~2..0w~2..0w", + Timestamp = io_lib:format(Format, [Year, Month, Day, Hour, Minute, Second]), + {ok, PackageString} = package_string(PackageID), + FileName = string:join([Timestamp, PackageString, "log"], "."), + filename:join(Dir, FileName). + + +-spec userconf(zx:user_id()) -> file:filename(). + +userconf({Realm, UserName}) -> + filename:join(path(etc, Realm), UserName ++ ".user"). -spec force_dir(Path) -> Result @@ -141,14 +168,20 @@ force_dir(Path) -> | {error, Reason :: file:posix()}. mktemp_dir({Realm, Name}) -> - Rand = integer_to_list(binary:decode_unsigned(crypto:strong_rand_bytes(8)), 36), - TempDir = filename:join(path(etc, Realm, Name), Rand), + Rand = random_string(), + TempDir = filename:join(path(tmp, Realm, Name), Rand), case force_dir(TempDir) of ok -> {ok, TempDir}; Error -> Error end. +-spec random_string() -> string(). + +random_string() -> + integer_to_list(binary:decode_unsigned(crypto:strong_rand_bytes(8)), 36). + + -spec list_realms() -> [zx:realm()]. %% @private %% Check the filesystem for etc/[Realm Name]/realm.conf files. @@ -546,9 +579,9 @@ package_string(_) -> {error, invalid_package_id}. --spec zsp_name(PackageID) -> ZrpFileName +-spec zsp_name(PackageID) -> ZspFileName when PackageID :: zx:package_id(), - ZrpFileName :: file:filename(). + ZspFileName :: file:filename(). %% @private %% Map a PackageID to its correct .zsp package file name. @@ -563,6 +596,12 @@ zsp_path(PackageID = {Realm, _, _}) -> filename:join(path(zsp, Realm), zsp_name(PackageID)). +-spec print_user({zx:user_name(), zx:real_name(), [zx:contact_info()]}) -> ok. + +print_user({UserName, RealName, [{"email", Email}]}) -> + io:format("~ts (~ts <~ts>) ~n", [UserName, RealName, Email]). + + -spec find_latest_compatible(Version, Versions) -> Result when Version :: zx:version(), Versions :: [zx:version()], @@ -582,6 +621,8 @@ find_latest_compatible(Version, Versions) -> latest_compatible(Version, Descending). +latest_compatible(_, []) -> + not_found; latest_compatible({z, z, z}, Versions) -> {ok, hd(Versions)}; latest_compatible({X, z, z}, Versions) -> @@ -607,7 +648,7 @@ latest_compatible(Version, Versions) -> %% True to its name, tells whether a package's install directory is found. installed(PackageID) -> - filelib:is_dir(path(lib, PackageID)). + filelib:is_dir(ppath(lib, PackageID)). -spec realm_conf(Realm) -> Path @@ -665,25 +706,38 @@ build() -> ok. --spec rm_rf(file:filename()) -> ok | {error, file:posix()}. +-spec rm_rf(Target) -> Result + when Target :: file:filename(), + Result :: ok | {error, file:posix()}. %% @private %% Recursively remove files and directories. Equivalent to `rm -rf'. %% Does not return an error on a nonexistant path. -rm_rf(Path) -> - case filelib:is_dir(Path) of +rm_rf(Target) -> + case filelib:is_dir(Target) of true -> - Pattern = filename:join(Path, "**"), + Pattern = filename:join(Target, "**"), Contents = lists:reverse(lists:sort(filelib:wildcard(Pattern))), - ok = lists:foreach(fun rm/1, Contents), - file:del_dir(Path); + Targets = rm_filter(Contents), + ok = lists:foreach(fun rm/1, Targets), + case file:list_dir(Target) of + {ok, []} -> file:del_dir(Target); + _ -> ok + end; false -> - case filelib:is_regular(Path) of - true -> file:delete(Path); + case filelib:is_regular(Target) of + true -> file:delete(Target); false -> ok - end + end end. +rm_filter(Contents) -> + P1 = path(lib, "otpr", "zx"), + P2 = path(lib, "otpr", "zomp"), + F1 = fun(C) -> not lists:prefix(P1, C) end, + F2 = fun(C) -> not lists:prefix(P2, C) end, + lists:filter(F2, lists:filter(F1, Contents)). + -spec rm(file:filename()) -> ok | {error, file:posix()}. %% @private @@ -696,6 +750,15 @@ rm(Path) -> end. +-spec enqueue_unique(term(), queue:queue()) -> queue:queue(). + +enqueue_unique(Term, Queue) -> + case queue:member(Term, Queue) of + true -> Queue; + false -> queue:in(Term, Queue) + end. + + -spec time_diff(Before, After) -> Diff when Before :: calendar:datetime(), After :: calendar:datetime(), diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_local.erl similarity index 77% rename from zomp/lib/otpr/zx/0.1.0/src/zx_local.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_local.erl index d941b34..ec14c6d 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_local.erl @@ -6,23 +6,25 @@ %%% @end -module(zx_local). +-vsn("0.2.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). -export([initialize/0, set_version/1, list_realms/0, list_packages/1, list_versions/1, - latest/1, + latest/1, provides/1, + list_keys/0, list_sysops/1, set_dep/1, list_deps/0, list_deps/1, drop_dep/1, verup/1, package/1, update_app_file/0, - import_realm/1, drop_realm/1, - takeover/1, abdicate/1, set_timeout/1, add_mirror/0, drop_mirror/0, - create_project/0, create_plt/0, dialyze/0, + import_realm/1, drop_realm/1, logpath/2, + set_timeout/1, add_mirror/0, drop_mirror/0, + create_project/0, grow_a_pair/0, grow_a_pair/2, drop_key/1, create_user/0, create_userfile/0, export_user/0, import_user/1, create_realm/0, create_realmfile/0, create_realmfile/1]). --export([select_user/1, select_private_key/1]). +-export([select_realm/0, create_user/1, select_private_key/1]). -include("zx_logger.hrl"). @@ -35,11 +37,11 @@ keys = none :: none | [zx:key_name()]}). -record(realm_init, - {realm = none :: none | zx:realm(), - addr = none :: none | string() | inet:ip_address(), - port = none :: none | inet:port_number(), - sysop = none :: none | #user_data{}, - url = none :: none | string()}). + {realm = none :: none | zx:realm(), + sysop = none :: none | #user_data{}, + url = "" :: none | string(), + addr = none :: none | string() | inet:ip_address(), + port = 11311 :: none | inet:port_number()}). -record(project, {type = none :: none | app | lib | escript, @@ -72,7 +74,7 @@ initialize() -> initialize(P = #project{type = none}) -> initialize(P#project{type = ask_project_type()}); initialize(P = #project{type = escript}) -> - ok = log(warning, "escripts cannot be initialized."), + ok = tell(warning, "escripts cannot be initialized."), initialize(P#project{type = ask_project_type()}); initialize(P = #project{type = app, id = none}) -> ID = {_, AppMod, _} = ask_package_id(), @@ -139,17 +141,37 @@ initialize_check(P = #project{id = PackageID}) -> "Two packages with the same name cannot exist in a single realm.~n" "Please pick another name.~n", ok = io:format(Message), - initialize(P#project{id = none}) + initialize(P#project{id = none}); + maybe -> + Message = + "~nHmmm...~n" + "zx_daemon wasn't able to find out (network problem?) whether this " + "package already exists.~n" + "What do?~", + ok = io:format(Message), + Options = [{"Continue anyway.", 1}, + {"Check again.", 2}, + {"Abort and do it later.", 3}], + case zx_tty:selet(Options) of + 1 -> zompify(P); + 2 -> initialize_check(P); + 3 -> ok + end end. zompify(#project{type = Type, id = ID, prefix = Prefix, appmod = AM, deps = Deps}) -> ok = initialize_app_file(ID, AM), - Data = #{package_id => ID, deps => Deps, type => Type, prefix => Prefix}, + Data = + #{package_id => ID, + deps => Deps, + type => Type, + prefix => Prefix, + tags => []}, ok = zx_lib:write_project_meta(Data), ok = ensure_emakefile(), {ok, PackageString} = zx_lib:package_string(ID), - ok = log(info, "Project ~ts initialized.", [PackageString]), + ok = tell("Project ~ts initialized.", [PackageString]), Message = "~nNOTICE:~n" "This project is currently listed as having no dependencies.~n" @@ -184,12 +206,12 @@ ensure_emakefile() -> update_source_vsn(Version) -> {ok, VersionString} = zx_lib:version_to_string(Version), AddF = "sed -i 's/^-module(.*)\\.$/&\\n-vsn(\"~s\")./' $(grep -L '^-vsn(' src/*)", - SubF = "sed -i 's/-vsn(.*$/-vsn(\"~s\")./' $(grep -l '^-vsn(' src/*)", + SubF = "sed -i 's/^-vsn(.*$/-vsn(\"~s\")./' $(grep -l '^-vsn(' src/*)", Add = lists:flatten(io_lib:format(AddF, [VersionString])), Sub = lists:flatten(io_lib:format(SubF, [VersionString])), _ = os:cmd(Add), _ = os:cmd(Sub), - log(info, "Source version attributes set"). + ok. -spec update_app_vsn(zx:name(), zx:version()) -> ok. @@ -240,7 +262,7 @@ initialize_app_file({_, Name, Version}, AppMod) -> Store = fun(T, L) -> lists:keystore(element(1, T), 1, L, T) end, AppData = lists:foldl(Store, RawAppData, Properties), AppProfile = {application, AppName, AppData}, - ok = log(info, "Writing app file: ~ts", [AppFile]), + ok = tell("Writing app file: ~ts", [AppFile]), zx_lib:write_terms(AppFile, [AppProfile]). @@ -252,7 +274,6 @@ update_app_file() -> Error -> Error end. - update_app_file(Meta) -> {_, Name, Version} = maps:get(package_id, Meta), {ok, VersionString} = zx_lib:version_to_string(Version), @@ -277,7 +298,7 @@ update_app_file(Meta) -> AppData = lists:foldl(Store, RawAppData, Properties), AppProfile = {application, AppName, AppData}, ok = zx_lib:write_terms(AppFile, [AppProfile]), - log(info, "Writing app file: ~ts", [AppFile]). + tell("Writing app file: ~ts", [AppFile]). -spec set_version(VersionString) -> zx:outcome() @@ -323,9 +344,9 @@ update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> ok = update_app_vsn(Name, NewVersion), {ok, OldVS} = zx_lib:version_to_string(OldVersion), {ok, NewVS} = zx_lib:version_to_string(NewVersion), - log(info, "Version changed from ~s to ~s.", [OldVS, NewVS]); + tell("Version changed from ~s to ~s~n.", [OldVS, NewVS]); Error -> - ok = log(error, "Write to zomp.meta failed with: ~160tp", [Error]), + ok = tell(error, "Write to zomp.meta failed with: ~160tp", [Error]), Error end. @@ -336,7 +357,7 @@ update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> list_realms() -> case zx_lib:list_realms() of - [] -> io:format("Oh noes! No realms are configured!~n"); + [] -> tell(error, "Oh noes! No realms are configured!~n"); Realms -> lists:foreach(fun(R) -> io:format("~ts~n", [R]) end, Realms) end. @@ -349,7 +370,7 @@ list_realms() -> list_packages(Realm) -> {ok, ID} = zx_daemon:list(Realm), case wait_result(ID) of - {ok, []} -> io:format("No packages available.~n"); + {ok, []} -> tell(error, "No packages available.~n"); {ok, Packages} -> lists:foreach(print_package(Realm), Packages); {error, bad_realm} -> {error, "Unconfigured realm or bad realm name.", 22}; {error, timeout} -> {error, "Request timed out.", 62}; @@ -375,7 +396,7 @@ list_versions(PackageString) -> list_versions2({Realm, Name, Version}) -> {ok, ID} = zx_daemon:list(Realm, Name, Version), case wait_result(ID) of - {ok, []} -> io:format("No versions available.~n"); + {ok, []} -> tell(error, "No versions available.~n"); {ok, Versions} -> lists:foreach(fun print_version/1, Versions); {error, bad_realm} -> {error, "Bad realm name.", 22}; {error, bad_package} -> {error, "Bad package name.", 22}; @@ -404,10 +425,51 @@ latest2(PackageID) -> end. -wait_result(ID) -> - receive - {result, ID, Result} -> Result - after 5000 -> {error, timeout} +-spec provides(Module :: string()) -> zx:outcome(). + +provides(Module) -> + Realms = zx_lib:list_realms(), + MakeRequest = + fun(Realm) -> + {ok, ID} = zx_daemon:provides(Realm, Module), + ID + end, + IDs = [MakeRequest(R) || R <- Realms], + case wait_results(IDs) of + {ok, Results} -> + Packages = lists:append([R || {_, {ok, R}} <- Results]), + lists:foreach(fun print_packages/1, Packages); + Error -> + Error + end. + +print_packages(PackageID) -> + {ok, PackageString} = zx_lib:package_string(PackageID), + io:format("~ts~n", [PackageString]). + + +-spec list_keys() -> zx:outcome(). + +list_keys() -> + Realm = select_realm(), + UserName = select_user(Realm), + {ok, ID} = zx_daemon:list_keys({Realm, UserName}), + case wait_result(ID) of + {ok, RemoteKeys} -> + Print = fun(KN) -> io:format("~ts~n", [KN]) end, + lists:foreach(Print, RemoteKeys); + Error -> + Error + end. + + +-spec list_sysops(zx:realm()) -> zx:outcome(). + +list_sysops(Realm) -> + {ok, ID} = zx_daemon:list_sysops(Realm), + case wait_result(ID) of + {ok, Sysops} -> lists:foreach(fun zx_lib:print_user/1, Sysops); + Error -> Error end. @@ -422,7 +484,7 @@ import_realm(Path) -> {ok, Data} -> Digest = crypto:hash(sha512, Data), Text = integer_to_list(binary:decode_unsigned(Digest, big), 16), - ok = log(info, "SHA-512 of ~ts: ~ts", [Path, Text]), + ok = tell("SHA-512 of ~ts: ~ts", [Path, Text]), import_realm2(Data); {error, enoent} -> {error, "Realm bundle (.zrf) does not exist.", 2}; @@ -436,15 +498,14 @@ import_realm(Path) -> import_realm2(Data) -> case zx_lib:b_to_t(Data) of - {ok, {RealmConf, KeyDER}} -> + {ok, {RealmConf, PubBin}} -> Realm = maps:get(realm, RealmConf), ok = make_realm_dirs(Realm), ConfPath = zx_lib:realm_conf(Realm), ok = zx_lib:write_terms(ConfPath, maps:to_list(RealmConf)), - KeyName = maps:get(key, RealmConf), - KeyPath = zx_key:path(public, {Realm, KeyName}), - ok = file:write_file(KeyPath, KeyDER), - log(info, "Added realm ~ts.", [Realm]); + KeyHash = maps:get(key, RealmConf), + ok = zx_daemon:register_key(Realm, {KeyHash, {none, PubBin}, none}), + tell("Added realm ~ts.", [Realm]); error -> {error, "Invalid .zrf file.", 84} end. @@ -498,11 +559,11 @@ set_dep3(PackageID = {Realm, Name, NewVersion}, Deps, Meta) -> Message = "Updating dep ~ts to ~ts", {ok, OldPS} = zx_lib:package_string({Realm, Name, OldVersion}), {ok, NewPS} = zx_lib:package_string({Realm, Name, NewVersion}), - ok = log(info, Message, [OldPS, NewPS]), + ok = tell(Message, [OldPS, NewPS]), [PackageID | Rest]; {[], Deps} -> {ok, PackageString} = zx_lib:package_string(PackageID), - ok = log(info, "Adding dep ~ts", [PackageString]), + ok = tell("Adding dep ~ts", [PackageString]), [PackageID | Deps] end, NewMeta = maps:put(deps, NewDeps, Meta), @@ -541,25 +602,43 @@ list_deps(PackageString) -> {ok, {_, _, {_, _, z}}} -> {error, "Packages must be fully specified; no partial versions.", 22}; {ok, PackageID} -> - log(info, "Phooey! list_deps(~16tp) isn't yet implemented!", [PackageID]); + list_deps2(PackageID); {error, invalid_package_string} -> {error, "Invalid package string.", 22} end. +list_deps2(PackageID) -> + {ok, ID} = zx_daemon:list_deps(PackageID), + case wait_result(ID) of + {ok, Deps} -> lists:foreach(fun print_package_id/1, Deps); + Error -> Error + end. --spec drop_dep(zx:package_id()) -> ok. -%% @private -%% Remove the indicate dependency from the local project's zomp.meta record. +print_package_id(PackageID) -> + {ok, PackageString} = zx_lib:package_string(PackageID), + io:format("~ts~n", [PackageString]). -drop_dep(PackageID) -> + +-spec drop_dep(PackageString :: string()) -> ok. + +drop_dep(PackageString) -> + case zx_lib:package_id(PackageString) of + {ok, PackageID} -> + drop_dep2(PackageID); + {error, invalid_package_string} -> + Message = "~tp is an invalid package string", + tell(error, Message, [PackageString]) + end. + +drop_dep2(PackageID) -> {ok, PackageString} = zx_lib:package_string(PackageID), Meta = case zx_lib:read_project_meta() of {ok, M} -> M; {error, enoent} -> - ok = log(error, "zomp.meta not found. Is this a project directory?"), - halt(1) + ok = io:format("zomp.meta not found. Is this a project directory?~n"), + init:stop(1) end, Deps = maps:get(deps, Meta), case lists:member(PackageID, Deps) of @@ -567,10 +646,9 @@ drop_dep(PackageID) -> NewDeps = lists:delete(PackageID, Deps), NewMeta = maps:put(deps, NewDeps, Meta), ok = zx_lib:write_project_meta(NewMeta), - Message = "~ts removed from dependencies.", - log(info, Message, [PackageString]); + tell("~ts removed from dependencies.", [PackageString]); false -> - log(info, "~ts is not a dependency.", [PackageString]) + io:format("~ts is not a dependency. Forget the version?~n", [PackageString]) end. @@ -635,64 +713,23 @@ version_up(patch, {Realm, Name, OldVersion = {Major, Minor, Patch}}, OldMeta) -> %% @private %% Turn a target project directory into a package, prompting the user for appropriate %% key selection or generation actions along the way. -%% TODO: Add user selection in the case more than one user with private keys exists. package(TargetDir) -> - ok = log(info, "Packaging ~ts", [TargetDir]), + ok = tell("Packaging ~ts", [TargetDir]), {ok, Meta} = zx_lib:read_project_meta(TargetDir), - PackageID = {Realm, _, _} = maps:get(package_id, Meta), - Deps = maps:get(deps, Meta), + {Realm, _, _} = maps:get(package_id, Meta), UserName = select_user(Realm), - KeyName = select_private_key({Realm, UserName}), - ok = log(info, "Using key: ~ts/~ts", [Realm, KeyName]), - {ok, PackageString} = zx_lib:package_string(PackageID), - ZspFile = PackageString ++ ".zsp", - case filelib:is_regular(ZspFile) of - false -> package2(TargetDir, PackageID, Deps, KeyName, ZspFile); - true -> {error, "Package file already exists. Aborting", 17} + case select_private_key({Realm, UserName}) of + {ok, Key} -> package2(TargetDir, Key); + error -> {error, "User has no private keys on the local system.", 1} end. -package2(TargetDir, PackageID, Deps, KeyName, ZspFile) -> - ok = remove_binaries(TargetDir), - CrashDump = filename:join(TargetDir, "erl_crash.dump"), - ok = - case filelib:is_regular(CrashDump) of - true -> file:delete(CrashDump); - false -> ok - end, - {ok, Everything} = file:list_dir(TargetDir), - DotFiles = filelib:wildcard(".*", TargetDir), - Targets = lists:subtract(Everything, DotFiles), - {ok, CWD} = file:get_cwd(), - ok = file:set_cwd(TargetDir), - Grep = "grep -oP '^-module\\(\\K[^)]+' src/* | cut -d: -f2", - Modules = string:lexemes(os:cmd(Grep), "\n"), - TarGzPath = filename:join(zx_lib:path(tmp), ZspFile ++ ".tgz"), - ok = erl_tar:create(TarGzPath, Targets, [compressed]), - {ok, TgzBin} = file:read_file(TarGzPath), - ok = file:delete(TarGzPath), - MetaBin = term_to_binary({PackageID, KeyName, Deps, Modules}), - MetaSize = byte_size(MetaBin), - SignMe = <>, - {ok, Key} = zx_key:load(private, {element(1, PackageID), KeyName}), - Sig = public_key:sign(SignMe, sha512, Key), - SigSize = byte_size(Sig), - ZspData = <>, - ok = file:set_cwd(CWD), - ok = file:write_file(ZspFile, ZspData), - log(info, "Wrote archive ~ts", [ZspFile]). - - --spec remove_binaries(TargetDir) -> ok - when TargetDir :: file:filename(). -%% @private -%% Procedure to delete all .beam and .ez files from a given directory starting at -%% TargetDir. Called as part of the pre-packaging sanitization procedure. - -remove_binaries(TargetDir) -> - Beams = filelib:wildcard("**/*.{beam,ez}", TargetDir), - ToDelete = [filename:join(TargetDir, Beam) || Beam <- Beams], - lists:foreach(fun file:delete/1, ToDelete). +package2(TargetDir, Key) -> + case zx_zsp:pack(TargetDir, Key) of + {ok, Path} -> tell("Wrote archive ~ts", [Path]); + {error, eexists} -> {error, "Package file already exists. Aborting", 17}; + Error -> Error + end. -spec create_project() -> ok. @@ -831,7 +868,7 @@ create(P = #project{type = escript, copyright = CR, c_email = CEmail}) -> Instructions = - "~nLIBRARY DATA CONFIRMATION~n" + "~nESCRIPT DATA CONFIRMATION~n" "[1] Type : escript~n" "[2] Project Name : ~ts~n" "[3] Script : ~ts~n" @@ -869,7 +906,22 @@ create_check(P = #project{id = PackageID}) -> "Two packages with the same name cannot exist in a single realm.~n" "Please pick another name.~n", ok = io:format(Message), - create(P#project{id = none}) + create(P#project{id = none}); + maybe -> + Message = + "~nHmmm...~n" + "zx_daemon wasn't able to find out (network problem?) whether this " + "package already exists.~n" + "What do?~", + ok = io:format(Message), + Options = [{"Continue anyway.", 1}, + {"Check again.", 2}, + {"Abort and do it later.", 3}], + case zx_tty:selet(Options) of + 1 -> zompify(P); + 2 -> initialize_check(P); + 3 -> ok + end end. @@ -894,8 +946,8 @@ create_project(#project{type = escript, ok = file:write_file(File, Cooked), Message = "Escript \"~ts\" written to ~ts.~n" - "You may want to change the file mode to add \"execute\" permissions.", - log(info, Message, [Name, File]); + "You may need to change the file mode to add \"execute\" permissions.~n", + io:format(Message, [Name, File]); create_project(P = #project{id = {_, Name, _}}) -> case file:make_dir(Name) of ok -> @@ -981,7 +1033,7 @@ substitute(Raw, [{Pattern, Value} | Rest]) -> Cooking = string:replace(Raw, Pattern, Value, all), substitute(Cooking, Rest); substitute(Cooked, []) -> - unicode:characters_to_list(Cooked, utf8). + unicode:characters_to_binary(Cooked, utf8). license("") -> ""; @@ -1012,7 +1064,7 @@ ask_project_type() -> "Select a project type.~n" "Remember, an \"application\" in Erlang is anything that needs to be started " "to work, even if it acts in a supporting role.~n" - "(Note that escripts cannot be initialized as packaged projects.~n" + "(Note that escripts cannot be packaged.)~n" "[1] Application~n" "[2] Library~n" "[3] Escript~n", @@ -1047,23 +1099,52 @@ ask_script_name() -> ask_package_id() -> Instructions = - "~nPACKAGE ID~n" - "A package ID is a set of three strings separated by dashes that represent " - "a project's realm, package name, and version number.~n" - "Package IDs typically look like this: \"otpr-my_project-1.2.3\" or similar.~n" - "Omitted realms default to \"otpr\". Omitted versions default to \"0.1.0\".~n" - "(A formal definition is available here: " - "http://zxq9.com/projects/zomp/zx_usage.en.html#identifiers)~n", - RawPackageString = zx_tty:get_input(Instructions), - case zx_lib:package_id(RawPackageString) of - {ok, {R, N, {z, z, z}}} -> {R, N, {0, 1, 0}}; - {ok, {R, N, {X, z, z}}} -> {R, N, {X, 0, 0}}; - {ok, {R, N, {X, Y, z}}} -> {R, N, {X, Y, 0}}; - {ok, ID} -> ID; - {error, invalid_package_string} -> - Message = "Sorry, \"~tp\" is an invalid package ID. Try again.~n", - ok = io:format(Message, [RawPackageString]), - ask_package_id() + "~nPACKAGE REALM~n" + "Realms are how code is organized inside the Zomp repository system. " + "They are analogous to repository sources for aptitude.~n" + "The default realm is called \"otpr\" and is the main realm for FOSS code.~n" + "It is also possible to create your own realm with the command " + "`zx create realm` and host it directly with `zx run zomp`.~n" + "Realms do not have to be publicly visible, and packages in one realm can " + "depend on packages in any other.~n" + "For more information on realm and package naming see " + "http://zxq9.com/projects/zomp/zx_usage.en.html#identifiers~n" + "Configured realms:~n", + ok = io:format(Instructions), + {ok, Realm} = pick_realm(), + ask_package_id2(Realm). + +ask_package_id2(Realm) -> + Instructions = + "~nPACKAGE NAME~n" + "Your package needs to have a name. This will also be it's Erlang application " + "or library main interface module name.~n" + "Legal names are made from characters0 [a-z1-9_], and cannot start with a " + "number or underscore.~n", + PackageName = zx_tty:get_input(Instructions), + case zx_lib:valid_lower0_9(PackageName) of + true -> ask_package_id3(Realm, PackageName); + false -> ask_package_id2(Realm) + end. + +ask_package_id3(Realm, Name) -> + Instructions = + "~nVERSION~n" + "You can set a package version here if desired. Unversioned packaged start at " + "version 0.1.0 by default.~n" + "Note that version numbers must be valid semver numbers.~n", + case zx_tty:get_input(Instructions, [], "[0.1.0]") of + "" -> {Realm, Name, {0, 1, 0}}; + String -> + case zx_lib:string_to_version(String) of + {ok, {X, z, z}} -> {Realm, Name, {X, 0, 0}}; + {ok, {X, Y, z}} -> {Realm, Name, {X, Y, 0}}; + {ok, V} -> {Realm, Name, V}; + {error, invalid_version_string} -> + Message = "Sorry, \"~tp\" is an invalid version. Try again.~n", + ok = io:format(Message, [String]), + ask_package_id3(Realm, Name) + end end. @@ -1120,68 +1201,12 @@ ask_module() -> %% a chance to change their package's name or ignore the conflict, and report all module %% naming conflicts. -package_exists(_) -> - false. - - --spec create_plt() -> ok. -%% @private -%% Build a general plt file for Dialyzer based on the core Erland distro. -%% TODO: Make a per-package + dependencies version of this. -%% TODO: PLT build options. -%% TODO: Meaningful failure messages. - -create_plt() -> - PLT = default_plt(), - Template = - "dialyzer --build_plt" - " --output_plt ~ts" - " --apps asn1 reltool wx common_test crypto erts eunit inets" - " kernel mnesia public_key sasl ssh ssl stdlib", - Command = io_lib:format(Template, [PLT]), - Message = - "Generating PLT file and writing to: ~ts~n" - " There will be a list of \"unknown functions\" in the final output.~n" - " Don't panic. This is normal. Turtles all the way down, after all...", - ok = log(info, Message, [PLT]), - ok = log(info, "This may take a while. Patience is a virtue."), - Out = os:cmd(Command), - log(info, Out). - - --spec default_plt() -> file:filename(). - -default_plt() -> - filename:join(zx_lib:zomp_dir(), "basic.plt"). - - --spec dialyze() -> ok. -%% @private -%% Preps a copy of this script for typechecking with Dialyzer. -%% TODO: Create a package_id() based version of this to handle dialyzation of complex -%% projects. - -dialyze() -> - PLT = default_plt(), - ok = - case filelib:is_regular(PLT) of - true -> log(info, "Using PLT: ~ts", [PLT]); - false -> create_plt() - end, - Me = escript:script_name(), - EvilTwin = filename:join(zx_lib:path(tmp), filename:basename(Me ++ ".erl")), - ok = log(info, "Temporarily reconstructing ~ts as ~ts", [Me, EvilTwin]), - Sed = io_lib:format("sed 's/^#!.*$//' ~s > ~s", [Me, EvilTwin]), - ok = zx_lib:exec_shell(Sed), - ok = case dialyzer:run([{init_plt, PLT}, {from, src_code}, {files, [EvilTwin]}]) of - [] -> - io:format("Dialyzer found no errors and returned no warnings! Yay!~n"); - Warnings -> - Mine = [dialyzer:format_warning({Tag, {Me, Line}, Msg}) - || {Tag, {_, Line}, Msg} <- Warnings], - lists:foreach(fun io:format/1, Mine) - end, - file:delete(EvilTwin). +package_exists({Realm, Package, _}) -> + {ok, ID} = zx_daemon:list(Realm), + case wait_result(ID) of + {ok, Packages} -> lists:member(Package, Packages); + _ -> maybe + end. -spec grow_a_pair() -> zx:outcome(). @@ -1194,21 +1219,19 @@ grow_a_pair() -> Realm -> grow_a_pair(Realm) end. - grow_a_pair(Realm) -> UserName = select_user(Realm), grow_a_pair(Realm, UserName). - grow_a_pair(Realm, UserName) -> - KeyTag = zx_key:prompt_keygen(), - KeyName = UserName ++ "-" ++ KeyTag, - case zx_key:generate_rsa({Realm, KeyName}) of - ok -> - ok; - {error, exists} -> - ok = io:format("That key name already exists. Try something different.~n"), - grow_a_pair(Realm, UserName); + case zx_key:generate_rsa(Realm) of + {ok, PairHash} -> + UserConf = zx_lib:userconf({Realm, UserName}), + {ok, UserData} = file:consult(UserConf), + Keys = proplists:get_value(keys, UserData), + NewKeys = [PairHash | Keys], + NewUserData = lists:keystor(keys, 1, {keys, NewKeys}, UserData), + zx_lib:write_terms(UserConf, NewUserData); Error -> Error end. @@ -1224,10 +1247,10 @@ drop_key({Realm, KeyName}) -> Pattern = filename:join(zx_lib:path(key, Realm), KeyName ++ ".{key,pub}.der"), case filelib:wildcard(Pattern) of [] -> - log(warning, "Key ~ts/~ts not found", [Realm, KeyName]); + tell(warning, "Key ~ts/~ts not found", [Realm, KeyName]); Files -> ok = lists:foreach(fun file:delete/1, Files), - log(info, "Keyset ~ts/~ts removed", [Realm, KeyName]) + tell(info, "Keyset ~ts/~ts removed", [Realm, KeyName]) end. @@ -1238,7 +1261,7 @@ drop_key({Realm, KeyName}) -> %% realm file to the user. create_realm() -> - ok = log(info, "WOOHOO! Making a new realm!"), + ok = tell("WOOHOO! Making a new realm!"), create_realm(#realm_init{}). @@ -1267,7 +1290,8 @@ create_realm(R = #realm_init{realm = Realm, addr = Addr, port = Port, url = URL} "2" -> create_realm(R#realm_init{addr = none}); "3" -> create_realm(R#realm_init{port = none}); "4" -> create_realm(R#realm_init{url = none}); - "" -> store_realm(R); + "" -> + store_realm(R); _ -> ok = zx_tty:derp(), create_realm(R) @@ -1278,16 +1302,18 @@ store_realm(#realm_init{realm = Realm, addr = Addr, port = Port, url = URL, - sysop = Sysop = #user_data{username = UserName, - keys = [KeyName]}}) -> + sysop = #user_data{username = UserName, + realname = RealName, + keys = [KeyName], + contact_info = [ContactInfo]}}) -> ok = make_realm_dirs(Realm), RealmConf = - [{realm, Realm}, - {prime, {Addr, Port}}, - {sysop, UserName}, - {key, KeyName}, - {url, URL}], - ok = store_user(Sysop), + [{realm, Realm}, + {prime, {Addr, Port}}, + {sysop, {UserName, RealName, ContactInfo}}, + {key, KeyName}, + {url, URL}, + {timestamp, calendar:universal_time()}], RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"), ok = zx_lib:write_terms(RealmConfPath, RealmConf), ok = create_realmfile(Realm), @@ -1430,13 +1456,13 @@ ask_url() -> create_sysop(U = #user_data{username = none}) -> UserName = ask_username(), - KeyName = UserName ++ "-root", - create_sysop(U#user_data{username = UserName, keys = [KeyName]}); + create_sysop(U#user_data{username = UserName}); create_sysop(U = #user_data{realname = none}) -> create_sysop(U#user_data{realname = ask_realname()}); create_sysop(U = #user_data{contact_info = none}) -> create_sysop(U#user_data{contact_info = [{"email", ask_email()}]}); -create_sysop(U = #user_data{username = UserName, +create_sysop(U = #user_data{realm = Realm, + username = UserName, realname = RealName, contact_info = [{"email", Email}]}) -> Instructions = @@ -1451,7 +1477,11 @@ create_sysop(U = #user_data{username = UserName, "1" -> create_sysop(U#user_data{username = none}); "2" -> create_sysop(U#user_data{realname = none}); "3" -> create_sysop(U#user_data{contact_info = none}); - "" -> U; + "" -> + {ok, KeyHash} = zx_key:generate_rsa(Realm), + NewU = U#user_data{keys = [KeyHash]}, + ok = store_user(NewU), + NewU; _ -> ok = zx_tty:derp(), create_sysop(U) @@ -1461,26 +1491,31 @@ create_sysop(U = #user_data{username = UserName, -spec create_user() -> ok. create_user() -> - UserName = create_user(#user_data{}), - log(info, "User ~ts created.", [UserName]). + UserName = create_user2(#user_data{}), + tell("User ~ts created.", [UserName]). --spec create_user(#user_data{}) -> zx:user_name(). +-spec create_user(zx:realm()) -> zx:user_name(). -create_user(U = #user_data{realm = none}) -> +create_user(Realm) -> + create_user2(#user_data{realm = Realm}). + + +-spec create_user2(#user_data{}) -> zx:user_name(). + +create_user2(U = #user_data{realm = none}) -> case pick_realm() of - {ok, Realm} -> create_user(U#user_data{realm = Realm}); + {ok, Realm} -> create_user2(U#user_data{realm = Realm}); {error, no_realms} -> {error, "No realms configured.", 1} end; -create_user(U = #user_data{username = none}) -> +create_user2(U = #user_data{username = none}) -> UserName = ask_username(), - KeyName = UserName ++ "-1", - create_user(U#user_data{username = UserName, keys = [KeyName]}); -create_user(U = #user_data{realname = none}) -> - create_user(U#user_data{realname = ask_realname()}); -create_user(U = #user_data{contact_info = none}) -> - create_user(U#user_data{contact_info = [{"email", ask_email()}]}); -create_user(U = #user_data{realm = Realm, + create_user2(U#user_data{username = UserName}); +create_user2(U = #user_data{realname = none}) -> + create_user2(U#user_data{realname = ask_realname()}); +create_user2(U = #user_data{contact_info = none}) -> + create_user2(U#user_data{contact_info = [{"email", ask_email()}]}); +create_user2(U = #user_data{realm = Realm, username = UserName, realname = RealName, contact_info = [{"email", Email}]}) -> @@ -1494,16 +1529,17 @@ create_user(U = #user_data{realm = Realm, "Press a number to select something to change, or [ENTER] to accept.~n", ok = io:format(Instructions, [Realm, UserName, RealName, Email]), case zx_tty:get_input() of - "1" -> create_user(U#user_data{realm = none}); - "2" -> create_user(U#user_data{username = none}); - "3" -> create_user(U#user_data{realname = none}); - "4" -> create_user(U#user_data{contact_info = none}); + "1" -> create_user2(U#user_data{realm = none}); + "2" -> create_user2(U#user_data{username = none}); + "3" -> create_user2(U#user_data{realname = none}); + "4" -> create_user2(U#user_data{contact_info = none}); "" -> - ok = store_user(U), + {ok, KeyHash} = zx_key:generate_rsa(Realm), + ok = store_user(U#user_data{keys = [KeyHash]}), UserName; _ -> ok = zx_tty:derp(), - create_user(U) + create_user2(U) end. @@ -1513,16 +1549,16 @@ store_user(#user_data{realm = Realm, username = UserName, realname = RealName, contact_info = ContactInfo, - keys = KeyNames}) -> + keys = [KeyHash]}) -> UserConf = [{realm, Realm}, {username, UserName}, {realname, RealName}, {contact_info, ContactInfo}, - {keys, KeyNames}], - ok = gen_keys(Realm, KeyNames), - UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"), - zx_lib:write_terms(UserConfPath, UserConf). + {keys, [KeyHash]}], + Path = zx_lib:userconf({Realm, UserName}), + ok = filelib:ensure_dir(Path), + zx_lib:write_terms(Path, UserConf). -spec create_userfile() -> ok. @@ -1540,16 +1576,15 @@ create_userfile(Realm) -> create_userfile(Realm, UserName) -> - UserConf = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"), + UserConf = zx_lib:userconf({Realm, UserName}), {ok, UserData} = file:consult(UserConf), Keys = proplists:get_value(keys, UserData), Load = fun(KeyName, Acc) -> - case file:read_file(zx_key:path(public, {Realm, KeyName})) of + case zx_daemon:keybin(public, {Realm, KeyName}) of {ok, Der} -> - SHA512 = crypto:hash(sha512, Der), - Public = {SHA512, Der}, - [{KeyName, none, Public} | Acc]; + Public = {none, Der}, + [{KeyName, Public, none} | Acc]; _ -> Acc end @@ -1576,22 +1611,22 @@ export_user() -> export_user(Realm) -> UserName = select_user(Realm), - UserConf = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"), + UserConf = zx_lib:userconf({Realm, UserName}), {ok, UserData} = file:consult(UserConf), Keys = proplists:get_value(keys, UserData), Load = fun(KeyName, Acc) -> Key = - case file:read_file(zx_key:path(private, {Realm, KeyName})) of - {ok, KDer} -> {crypto:hash(sha512, KDer), KDer}; + case zx_daemon:keybin(private, {Realm, KeyName}) of + {ok, KDer} -> {none, KDer}; _ -> none end, Pub = - case file:read_file(zx_key:path(public, {Realm, KeyName})) of - {ok, PDer} -> {crypto:hash(sha512, PDer), PDer}; + case zx_daemon:keybin(public, {Realm, KeyName}) of + {ok, PDer} -> {none, PDer}; _ -> none end, - [{KeyName, Key, Pub} | Acc] + [{KeyName, Pub, Key} | Acc] end, KeyData = lists:foldl(Load, [], Keys), UserFile = Realm ++ "-" ++ UserName ++ ".zduf", @@ -1615,71 +1650,51 @@ import_user(Path) -> Error -> Error end. - import_user2(Bin) -> case zx_lib:b_to_t(Bin) of - {ok, {UserData, []}} -> - ok = log(info, "Note: This user file does not have any keys."), - import_user3(UserData, []); - {ok, {UserData, KeyData}} -> - import_user3(UserData, KeyData); + {ok, {NewUserData, []}} -> + ok = tell("Note: This user file does not have any keys."), + import_user3(NewUserData, []); + {ok, {NewUserData, KeyData}} -> + import_user3(NewUserData, KeyData); error -> {error, "Bad data. Is this really a legitimate .zduf or .zpuf?", 1} end. - -import_user3(UserData, KeyData) -> - Realm = proplists:get_value(realm, UserData), - UserName = proplists:get_value(username, UserData), - case filelib:is_dir(zx_lib:path(etc, Realm)) of - true -> - UserConf = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"), - ok = zx_lib:write_terms(UserConf, UserData), +import_user3(NewUserData, KeyData) -> + Realm = proplists:get_value(realm, NewUserData), + UserName = proplists:get_value(username, NewUserData), + UserConf = zx_lib:userconf({Realm, UserName}), + case file:consult(UserConf) of + {ok, OldUserData} -> + UpdatedUserData = merge_userconf(NewUserData, OldUserData, KeyData), + ok = zx_lib:write_terms(UserConf, UpdatedUserData), import_user4(Realm, UserName, KeyData); - false -> - {error, "User is from a realm which is not available locally.", 1} + {error, enoent} -> + case filelib:is_dir(filename:dirname(UserConf)) of + true -> + ok = zx_lib:write_terms(UserConf, NewUserData), + import_user4(Realm, UserName, KeyData); + false -> + ok = tell(error, "Realm ~tp is not configured.", [Realm]), + {error, bad_realm} + end end. +import_user4(Realm, UserName, Keys) -> + Register = fun(KeyData) -> zx_daemon:register_key({Realm, UserName}, KeyData) end, + ok = lists:foreach(Register, Keys), + tell(info, "Imported user ~ts to realm ~ts.", [UserName, Realm]). -import_user4(Realm, UserName, KeyData) -> - Write = - fun - ({KeyName, {KeySHA512, KeyDer}, none}) -> - KeySHA512 = crypto:hash(sha512, KeyDer), - KeyID = {Realm, KeyName}, - file:write_file(zx_key:path(private, KeyID), KeyDer); - ({KeyName, none, {PubSHA512, PubDer}}) -> - PubSHA512 = crypto:hash(sha512, PubDer), - KeyID = {Realm, KeyName}, - file:write_file(zx_key:path(public, KeyID), PubDer); - ({KeyName, {KeySHA512, KeyDer}, {PubSHA512, PubDer}}) -> - KeySHA512 = crypto:hash(sha512, KeyDer), - PubSHA512 = crypto:hash(sha512, PubDer), - KeyID = {Realm, KeyName}, - file:write_file(zx_key:path(private, KeyID), KeyDer), - file:write_file(zx_key:path(public, KeyID), PubDer) - end, - ok = lists:foreach(Write, KeyData), - log(info, "Imported user ~ts to realm ~ts.", [UserName, Realm]). - - --spec list_users(Realm) -> UserNames - when Realm :: zx:realm(), - UserNames :: [zx:user_name()]. - -list_users(Realm) -> - Pattern = filename:join(zx_lib:path(etc, Realm), "*.user"), - [filename:basename(UN, ".user") || UN <- filelib:wildcard(Pattern)]. - - --spec gen_keys(Realm, KeyNames) -> ok - when Realm :: zx:realm(), - KeyNames :: [zx:key_names()]. - -gen_keys(Realm, KeyNames) -> - ok = io:format("Generating keys. This might take a while, so settle in...~n"), - GenRSA = fun(KeyName) -> zx_key:generate_rsa({Realm, KeyName}) end, - lists:foreach(GenRSA, KeyNames). +merge_userconf(NewUserData, OldUserData, KeyData) -> + NewMap = maps:from_list(NewUserData), + OldMap = maps:from_list(OldUserData), + KeySet = sets:from_list([element(1, K) || K <- KeyData]), + NewKeySet = sets:from_list(maps:get(keys, NewMap)), + OldKeySet = sets:from_list(maps:get(keys, OldMap)), + UltimateKeySet = sets:intersection([KeySet, NewKeySet, OldKeySet]), + Final = maps:put(keys, sets:to_list(UltimateKeySet), maps:merge(OldMap, NewMap)), + maps:to_list(Final). -spec pick_realm() -> Result @@ -1689,7 +1704,7 @@ gen_keys(Realm, KeyNames) -> pick_realm() -> case zx_lib:list_realms() of [] -> - ok = log(warning, "No realms configured! Exiting..."), + ok = tell(warning, "No realms configured! Exiting..."), {error, no_realms}; Realms -> ok = io:format("Select a realm:~n"), @@ -1838,7 +1853,9 @@ ask_email_optional() -> check_email(Address) -> case string:lexemes(Address, "@") of [User, Host] -> - case {zx_lib:valid_lower0_9(User), zx_lib:valid_label(Host)} of + ValidUser = io_lib:printable_latin1_list(User), + ValidHost = io_lib:printable_latin1_list(Host), + case {ValidUser, ValidHost} of {true, true} -> ok; {false, true} -> @@ -1870,8 +1887,9 @@ email_instructions() -> %% Checks for remnants of a realm. realm_exists(Realm) -> - Managed = lists:member(Realm, zx_sys_conf:managed(zx_sys_conf:load())), - Dirs = [zx_lib:path(D, Realm) || D <- [etc, var, tmp, log, key, zsp, lib]], + {ok, MRs} = zx_daemon:conf(managed), + Managed = lists:member(Realm, MRs), + Dirs = [zx_lib:path(D, Realm) || D <- [etc, var, tmp, log, key, zsp]], Check = fun(D) -> filelib:is_file(D) end, Found = lists:any(Check, Dirs), Managed or Found. @@ -1888,19 +1906,6 @@ make_realm_dirs(Realm) -> lists:foreach(Make, Dirs). -%% FIXME -%-spec configure_zomp() -> ok. -% -%configure_zomp() -> -% ZompSettings = -% [{node, 16}, -% {vampire, 16}, -% {leaf, 256}, -% {listen_port, 11311}, -% {public_port, 11311}], -% io:format("~tw~n", [ZompSettings]). - - -spec create_realmfile() -> ok. create_realmfile() -> @@ -1912,14 +1917,13 @@ create_realmfile() -> create_realmfile(Realm) -> {ok, RealmConf} = zx_lib:load_realm_conf(Realm), - ok = log(info, "Realm found, creating realm file..."), + ok = tell("Realm found, creating realm file..."), KeyName = maps:get(key, RealmConf), - PubKeyPath = zx_key:path(public, {Realm, KeyName}), - {ok, PubDER} = file:read_file(PubKeyPath), + {ok, PubDER} = zx_daemon:keybin(public, {Realm, KeyName}), Blob = term_to_binary({RealmConf, PubDER}), ZRF = Realm ++ ".zrf", ok = file:write_file(ZRF, Blob), - log(info, "Realm conf file written to ~ts", [ZRF]). + tell("Realm conf file written to ~ts", [ZRF]). -spec drop_realm(zx:realm()) -> ok. @@ -1929,44 +1933,44 @@ drop_realm(Realm) -> true -> Message = "~nWARNING: Are you SURE you want to remove realm ~ts?~n" - "(Only \"Y\" will confirm this action.)~n", + "(Only \"YES\" will confirm this action.)~n", ok = io:format(Message, [Realm]), case zx_tty:get_input() of - "Y" -> - Dirs = [etc, var, tmp, log, key, zsp, lib], - RM = fun(D) -> ok = zx_lib:rm_rf(zx_lib:path(D, Realm)) end, - ok = lists:foreach(RM, Dirs), - ok = abdicate(Realm), - log(info, "All traces of realm ~ts have been removed."); + "YES" -> + ok = zx_daemon:drop_realm(Realm), + tell("All traces of realm ~ts have been removed.", [Realm]); _ -> - log(info, "Aborting.") + tell("Aborting.") end; false -> - log(info, "Could not find any trace of realm ~ts", [Realm]) + tell("Could not find any trace of realm ~ts", [Realm]) end. --spec takeover(zx:realm()) -> ok. -%% @private -%% Assume responsibilities as the prime node for the given realm. Only works if -%% the realm exists, of course. +-spec logpath(PackageString, Ago) -> zx:outcome() + when PackageString :: string(), + Ago :: pos_integer(). -takeover(Realm) -> - SysConf = zx_sys_conf:load(), - case zx_sys_conf:add_managed(Realm, SysConf) of - {ok, NewConf} -> zx_sys_conf:save(NewConf); - {error, unconfigured} -> log(error, "Cannot take over an unconfigured realm.") - end. - - --spec abdicate(zx:realm()) -> ok. - -abdicate(Realm) -> - SysConf = zx_sys_conf:load(), - case zx_sys_conf:rem_managed(Realm, SysConf) of - {ok, NewConf} -> zx_sys_conf:save(NewConf); - {error, unmanaged} -> log(error, "Cannot abdicate an unmanaged realm.") - end. +logpath(PackageString, Ago) when Ago > 0 -> + case zx_lib:package_id(PackageString) of + {ok, PackageID} -> + Path = zx_lib:ppath(log, PackageID), + ok = zx_lib:force_dir(Path), + {ok, Files} = file:list_dir(Path), + Count = length(Files), + case Ago =< Count of + true -> + LogPath = lists:nth(Ago, lists:reverse(lists:sort(Files))), + io:format("~ts~n", [LogPath]); + false -> + io:format("There are only ~w log files!~n", [Count]) + end; + Error -> + Error + end; +logpath(_, Ago) when Ago < 1 -> + Message = "I'm awesome, but even I can't remember the future. ~w runs ago...?~n", + ok = io:format(Message, [Ago]). -spec set_timeout(string()) -> zx:outcome(). @@ -1983,14 +1987,13 @@ set_timeout(String) -> -spec add_mirror() -> ok. add_mirror() -> - ok = log(info, "Adding a mirror to the local configuration..."), - SysConf = zx_sys_conf:load(), + ok = tell("Adding a mirror to the local configuration..."), ok = - case zx_sys_conf:mirrors(SysConf) of - [] -> - ok; - Current -> - ok = io:format("Current mirrors:~n"), + case zx_daemon:conf(mirrors) of + {ok, []} -> + io:format("~nThere are NO MIRRORS currently.~n"); + {ok, Current} -> + ok = io:format("~nCurrent mirrors:~n"), Print = fun({A, P}) -> S = stringify_address(A), @@ -2000,26 +2003,16 @@ add_mirror() -> end, Host = ask_addr(), Port = ask_port(), - zx_sys_conf:save(zx_sys_conf:add_mirror({Host, Port}, SysConf)). + zx_daemon:add_mirror({Host, Port}). -spec drop_mirror() -> ok. drop_mirror() -> - ok = log(info, "Removing mirrors from the local configuration..."), - SysConf = zx_sys_conf:load(), - zx_sys_conf:save(drop_mirror(SysConf)). - - --spec drop_mirror(zx_sys_conf:data()) -> zx_sys_conf:data(). - -drop_mirror(SysConf) -> - case zx_sys_conf:mirrors(SysConf) of - [] -> - ok = log(info, "No mirrors to drop!"), - SysConf; - Current -> - ok = io:format("Pick a host to drop:~n"), + case zx_daemon:list_mirrors() of + {ok, []} -> + io:format("No mirrors to drop!~n"); + {ok, Current} -> Optionize = fun(Host = {A, P}) -> Label = io_lib:format("~s:~w", [stringify_address(A), P]), @@ -2027,7 +2020,7 @@ drop_mirror(SysConf) -> end, Options = lists:map(Optionize, Current), Selection = zx_tty:select(Options), - zx_sys_conf:rem_mirror(Selection, SysConf) + zx_daemon:drop_mirror(Selection) end. @@ -2044,11 +2037,13 @@ select_realm() -> -spec select_user(zx:realm()) -> zx:user_name(). select_user(Realm) -> - case list_users(Realm) of + Pattern = filename:join(zx_lib:path(etc, Realm), "*.user"), + LocalUsers = [filename:basename(UN, ".user") || UN <- filelib:wildcard(Pattern)], + case LocalUsers of [] -> Message = "A user record is required to complete this action. Creating now...", - ok = log(info, Message), + ok = tell(Message), create_user(#user_data{realm = Realm}); [UserName] -> UserName; @@ -2057,14 +2052,54 @@ select_user(Realm) -> end. --spec select_private_key(zx:user_id()) -> zx:key_name(). +-spec select_private_key(zx:user_id()) -> Result + when Result :: {ok, {zx:key_name(), public_key:rsa_private_key()}} + | error. -select_private_key(UserID = {Realm, UserName}) -> - KeyDir = zx_lib:path(key, Realm), - ok = zx_lib:force_dir(KeyDir), - Pattern = filename:join(KeyDir, UserName ++ "-*.key.der"), - case [filename:basename(F, ".key.der") || F <- filelib:wildcard(Pattern)] of - [] -> zx_key:prompt_keygen(UserID); - [KeyName] -> KeyName; - KeyNames -> zx_tty:select_string(KeyNames) +select_private_key(UserID) -> + ConfPath = zx_lib:userconf(UserID), + {ok, UserConf} = file:consult(ConfPath), + Keys = proplists:get_value(keys, UserConf), + Realm = element(1, UserID), + select_private_key2(Realm, Keys). + +select_private_key2(Realm, [KeyName | Rest]) -> + case zx_daemon:get_key(private, {Realm, KeyName}) of + {ok, Key} -> + {ok, {KeyName, Key}}; + {error, Reason} -> + ok = tell(warning, "zx_daemon:get_key/2 failed with: ~tp", [Reason]), + select_private_key2(Realm, Rest) + end; +select_private_key2(_, []) -> + error. + + + +%%% Utility Functions + +wait_result(ID) -> + receive + {result, ID, Result} -> Result; + Message -> {error, {unexpected, Message}} + after 5000 -> {error, timeout} + end. + + +wait_results(IDs) -> + wait_results(IDs, []). + +wait_results([], Results) -> + {ok, Results}; +wait_results(IDs, Results) -> + receive + {result, ID, Result} -> + case lists:member(ID, IDs) of + true -> wait_results(lists:delete(ID, IDs), [{ID, Result} | Results]); + false -> {error, {unexpected, {result, ID, Result}}, Results} + end; + Message -> + {error, {unexpected, Message}} + after 5000 -> + {error, timeout} end. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_net.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_net.erl similarity index 95% rename from zomp/lib/otpr/zx/0.1.0/src/zx_net.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_net.erl index c74291c..f694b62 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_net.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_net.erl @@ -5,6 +5,7 @@ %%% @end -module(zx_net). +-vsn("0.2.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). @@ -170,6 +171,8 @@ rx(Socket, Timeout) -> %% @doc %% Abstract large receives with a fixed timeout between segments and progress update %% messages to listening processes. +%% NOTE: The gen_tcp:send/2 on the second line of this function mimics the behavior of +%% the {packet, 4} option which is standard outside of bulk RX/TX. rx(Socket, Timeout, Watchers) -> ok = inet:setopts(Socket, [{active, once}, {packet, 0}]), @@ -230,7 +233,8 @@ 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(not_sysop) -> <<0:1, 19:7>>; +err_ex({retry, Seconds}) -> <<0:1, 20:7, Seconds:24>>; err_ex(Reason) -> [<<0:1, 127:7>>, io_lib:format("~tw", [Reason])]. @@ -252,5 +256,6 @@ 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, 19:7>>) -> not_sysop; +err_in(<<0:1, 20:7, Seconds:24>>) -> {retry, Seconds}; err_in(<<0:1, 127:7, Reason/binary>>) -> unicode:characters_to_list(Reason). diff --git a/zomp/lib/otpr/zx/0.2.0/src/zx_peer.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_peer.erl new file mode 100644 index 0000000..230ea5c --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_peer.erl @@ -0,0 +1,265 @@ +%%% @doc +%%% ZX Peer +%%% +%%% A process that handles connections from local ZX instances. +%%% These act as client processes within the local node, shuttling requests from +%%% other nodes to the zx_daemon, and receiving return responses and passing them +%%% back over the local socket. +%%% @end + +-module(zx_peer). +-vsn("0.2.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0"). + +-export([notify/3, takeover/2]). +-export([start/1]). +-export([start_link/1, init/2]). +-export([system_continue/3, system_terminate/4, + system_get_state/1, system_replace_state/2]). + +-include("zx_logger.hrl"). + +%%% Type and Record Definitions + +-record(s, {socket = none :: none | gen_tcp:socket()}). + +-type state() :: #s{}. + + +%%% Service Interface + +-spec notify(Pid, Channel, Message) -> ok + when Pid :: pid(), + Channel :: term(), + Message :: term(). + +notify(Pid, Channel, Message) -> + Pid ! {z_sub, Channel, Message}, + ok. + + +-spec takeover(Pid, ID) -> ok | timeout + when Pid :: pid(), + ID :: zx_daemon:id(). + +takeover(Pid, ID) -> + Ref = make_ref(), + Pid ! {takeover, Ref, ID}, + receive + {Ref, ok} -> ok + after 1000 -> timeout + end. + + +-spec start(ListenSocket) -> Result + when ListenSocket :: gen_tcp:socket(), + Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). + +start(ListenSocket) -> + zx_peer_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(). + +start_link(ListenSocket) -> + proc_lib:start_link(?MODULE, init, [self(), ListenSocket]). + + +-spec init(Parent, ListenSocket) -> no_return() + when Parent :: pid(), + ListenSocket :: gen_tcp:socket(). + +init(Parent, ListenSocket) -> + 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 zx_peer_man (so it can close it on a call to zx_peer_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), + State = #s{socket = Socket}, + loop(Parent, Debug, State); + {error, closed} -> + exit(normal) + end. + + +-spec loop(Parent, Debug, State) -> no_return() + when Parent :: pid(), + Debug :: [sys:dbg_opt()], + State :: state(). + +loop(Parent, Debug, State = #s{socket = Socket}) -> + receive + {tcp, Socket, Bin} -> + Result = handle_message(Bin), + ok = gen_tcp:send(Socket, Result), + loop(Parent, Debug, State); + {result, ID, Result} -> + Bin = term_to_binary({ID, Result}), + Message = <<0:8, Bin/binary>>, + ok = gen_tcp:send(Socket, Message), + loop(Parent, Debug, State); + {z_sub, Channel, Message} -> + Bin = term_to_binary({Channel, Message}), + Message = <<1:1, 1:7, Bin/binary>>, + ok = gen_tcp:send(Socket, Message), + loop(Parent, Debug, State); + {takeover, Ref, ID} -> + ok = gen_tcp:send(Socket, <<1:1, 1:7, ID:32>>), + zx_peer_man ! {Ref, ok}, + loop(Parent, Debug, State); + {new_proxy, Port} -> + Message = <<2:8, Port:16>>, + ok = gen_tcp:send(Socket, Message), + ok = zx_net:disconnect(Socket), + exit(normal); + {tcp_closed, Socket} -> + exit(normal); + {system, From, Request} -> + sys:handle_system_msg(Request, From, Parent, ?MODULE, Debug, State); + Unexpected -> + ok = log(warning, "~p Unexpected message: ~tp", [self(), Unexpected]), + loop(Parent, Debug, State) + end. + + +handle_message(<>) -> + Payload = binary_to_term(Bin, [safe]), + Result = + case Command of + 1 -> zx_daemon:subscribe(Payload); + 2 -> zx_daemon:unsubscribe(Payload); + 3 -> deferred(fun list/1, Payload); + 4 -> deferred(fun zx_daemon:latest/1, Payload); + 5 -> deferred(fun provides/1, Payload); + 6 -> deferred(fun zx_daemon:list_deps/1, Payload); + 7 -> deferred(fun zx_daemon:list_sysops/1, Payload); + 8 -> deferred(fun zx_daemon:fetch/1, Payload); + 9 -> zx_daemon:install(Payload); + 10 -> zx_daemon:build(Payload); + 11 -> zx_daemon:list_mirrors(); + 12 -> zx_daemon:add_mirror(Payload); + 13 -> zx_daemon:drop_mirror(Payload); + 14 -> register_key(Payload); + 15 -> get_key(Payload); + 16 -> keybin(Payload); + 17 -> zx_daemon:find_keypair(Payload); + 18 -> have_key(Payload); + 19 -> list_keys(Payload); + 20 -> zx_daemon:takeover(Payload); + 21 -> zx_daemon:abdicate(Payload); + 22 -> zx_daemon:drop_realm(Payload); + 23 -> deferred(fun zx_daemon:keychain/1, Payload) + end, + pack(Result). + + +deferred(Query, Payload) -> + {ok, ID} = Query(Payload), + receive + {result, ID, Result} -> Result + after 5000 -> {error, timeout} + end. + + +pack(ok) -> <<0:8>>; +pack({ok, Result}) -> <<0:8, (term_to_binary(Result))/binary>>; +pack({error, Reason}) -> zx_net:err_ex(Reason); +pack(done) -> <<0:8>>. + + +list({R, N, V}) -> zx_daemon:list(R, N, V); +list({R, N}) -> zx_daemon:list(R, N); +list(R) -> zx_daemon:list(R). + +provides({R, M}) -> zx_daemon:provides(R, M). + +register_key({O, D}) -> zx_daemon:register_key(O, D). + +get_key({T, K}) -> zx_daemon:get_key(T, K). + +keybin({T, K}) -> zx_daemon:keybin(T, K). + +have_key({T, K}) -> zx_daemon:have_key(T, K). + +list_keys({T, O}) -> zx_daemon:list_keys(T, O). + + +%%% OTP System Message Handling + +-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}. diff --git a/zomp/lib/otpr/zx/0.2.0/src/zx_peer_man.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_peer_man.erl new file mode 100644 index 0000000..6ad7619 --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_peer_man.erl @@ -0,0 +1,307 @@ +%%% @doc +%%% ZX Peer Manager +%%% +%%% Manages the peer connection aggregation subsystem for ZX. +%%% When ZX nodes need to connec to to get data from upstream Zomp nodes they proxy +%%% their connections through whatever ZX nodes is already providing this service +%%% locally. In addition to aggregating network resource usage, ZX also aggregates +%%% it also sequentializes (and effectively atomizes) local disk operations. +%%% @end + +-module(zx_peer_man). +-vsn("0.2.0"). +-behavior(gen_server). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0"). + +-export([listen/0, ignore/0, retire/1]). +-export([enroll/0, broadcast/2]). +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + +-include("zx_logger.hrl"). + +%%% Type and Record Definitions + + +-record(s, {listener = none :: none | gen_tcp:socket(), + secondary = none :: none | pid(), + peers = [] :: [{reference(), pid()}]}). + + +-type state() :: #s{}. + + + +%%% Service Interface + + +-spec listen() -> Result + when Result :: {ok, inet:port_number()} + | error. +%% @doc +%% Tell the service to start listening. The port number selection here is left up +%% to the host OS, so when it is determined then the number will be returned to the +%% caller (should always be zx_daemon calling). There shouldn't be any reasons why +%% this call should fail, so zx_daemon is expecting it to always succeed and errors +%% are not specified. + +listen() -> + gen_server:call(?MODULE, listen). + + +-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). + + +-spec retire(ID) -> ok | no_peers + when ID :: integer(). + +retire(ID) -> + gen_server:call(?MODULE, {retire, ID}). + + +-spec enroll() -> ok. +%% @private +%% zx_peer processes have to enroll themselves so that the zx_peer_man can monitor them +%% and know which one has been alive the longest. In the event that this node retires +%% or goes down it will need to designate a successor. Because there is no guarantee +%% that any of the code being executed by ZX is reliable (and may be calling halt(N) +%% or any other hard shutdown functions or breaks), successor designation occurs +%% pre-emptively and not just on shutdown. + +enroll() -> + gen_server:cast(?MODULE, {enroll, self()}). + + +-spec broadcast(Channel, Message) -> ok + when Channel :: term(), + Message :: term(). + +broadcast(Channel, Message) -> + gen_server:cast(?MODULE, {broadcast, Channel, Message}). + + +%%% Startup Functions + + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason :: term()}. +%% @private +%% This should only ever be called by zx_peers (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) -> + 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, _, State) -> + {Response, NewState} = do_listen(State), + {reply, Response, NewState}; +handle_call({retire, ID}, _, State) -> + Result = do_retire(ID, State), + {reply, Result, State}; +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({broadcast, Channel, Message}, State) -> + ok = do_broadcast(Channel, Message, 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, Info}, State) -> + NewState = handle_down(Mon, Pid, Info, 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(State) -> {Result, NewState} + when State :: state(), + Result :: {ok, inet:port_number()} + | error, + NewState :: state(). +%% @private +%% The "doer" procedure called when a "listen" message is received. + +do_listen(State = #s{listener = none}) -> + Options = + [inet6, + {ip, {0,0,0,0,0,0,0,1}}, + {active, true}, + {mode, binary}, + {keepalive, true}, + {reuseaddr, true}, + {packet, 4}], + {ok, Listener} = gen_tcp:listen(0, Options), + {ok, Port} = inet:port(Listener), + {ok, _} = zx_peer:start(Listener), + {{ok, Port}, State#s{listener = Listener}}; +do_listen(State) -> + ok = log(warning, "Already listening."), + {error, State}. + + +-spec do_enroll(Pid, State) -> NewState + when Pid :: pid(), + State :: state(), + NewState :: state(). + +do_enroll(Pid, State = #s{peers = []}) -> + ok = zx_peer:become_secondary(Pid), + Mon = monitor(process, Pid), + State#s{secondary = Pid, peers = [{Mon, Pid}]}; +do_enroll(Pid, State = #s{peers = Peers}) -> + Mon = monitor(process, Pid), + State#s{peers = [{Mon, Pid} | Peers]}. + + +do_broadcast(Channel, Message, #s{peers = Peers}) -> + Notify = fun({_, Pid}) -> zx_peer:notify(Pid, Channel, Message) end, + lists:foreach(Notify, Peers). + + +-spec handle_down(Mon, Pid, Info, State) -> NewState + when Mon :: reference(), + Pid :: pid(), + Info :: term(), + State :: state(), + NewState :: state(). + +handle_down(Mon, Pid, Info, State = #s{secondary = Pid, peers = Peers}) -> + Peer = {Mon, Pid}, + ok = log(info, "Secondary peer ~p retired with ~tp.", [Pid, Info]), + case lists:delete(Peer, Peers) of + [] -> + State#s{secondary = none, peers = []}; + NewPeers -> + {_, NextPid} = tl(NewPeers), + ok = zx_peer:become_secondary(NextPid), + State#s{secondary = NextPid, peers = NewPeers} + end; +handle_down(Mon, Pid, Info, State = #s{peers = Peers}) -> + Peer = {Mon, Pid}, + case lists:member(Peer, Peers) of + true -> + ok = log(info, "Peer ~p retired.", [Pid]), + State#s{peers = lists:delete(Peer, Peers)}; + false -> + Unexpected = {'DOWN', Mon, process, Pid, Info}, + ok = log(warning, "Unexpected info: ~160tp", [Unexpected]), + State + end. + + +-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}. + + +do_retire(_, #s{secondary = none}) -> halt; +do_retire(ID, #s{secondary = Pid}) -> zx_peer:takeover(Pid, ID). diff --git a/zomp/lib/otpr/zx/0.2.0/src/zx_peer_sup.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_peer_sup.erl new file mode 100644 index 0000000..4206e8a --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_peer_sup.erl @@ -0,0 +1,61 @@ +%%% @doc +%%% ZX Peer Supervisor +%%% +%%% This process supervises the peer socket handlers themselves. It is a peer of the +%%% zx_peer_man, and a child of the supervisor named zx_peers. +%%% @end + +-module(zx_peer_sup). +-vsn("0.2.0"). +-behaviour(supervisor). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0"). + +-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 zx_peer_man when es:listen/1 +%% is called, or the next listener at the request of the currently listening zx_peer +%% 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}, + Peer = {zx_peer, + {zx_peer, start_link, []}, + temporary, + brutal_kill, + worker, + [zx_peer]}, + {ok, {RestartStrategy, [Peer]}}. diff --git a/zomp/lib/otpr/zx/0.2.0/src/zx_peers.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_peers.erl new file mode 100644 index 0000000..fdc5b7e --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_peers.erl @@ -0,0 +1,49 @@ +%%% @doc +%%% ZX Peer Service Supervisor +%%% +%%% The service-level supervisor of the peer subsystem. +%%% The peer subsystem makes sure that external connections and write access to +%%% system resources are all passed through a single instance of zx_daemon. Once a +%%% zx_daemon recognizes that it is the first instance of ZX running on a system it +%%% declares itself the system proxy by writing a lock file in the main Zomp directory +%%% and opening a local port to listen to connections from other local ZX instances. +%%% @end + +-module(zx_peers). +-vsn("0.2.0"). +-behavior(supervisor). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0"). + +-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}, + PeerSup = {zx_peer_sup, + {zx_peer_sup, start_link, []}, + permanent, + 5000, + supervisor, + [zx_peer_sup]}, + PeerMan = {zx_peer_man, + {zx_peer_man, start_link, []}, + permanent, + 5000, + worker, + [zx_peer_man]}, + Children = [PeerSup, PeerMan], + {ok, {RestartStrategy, Children}}. diff --git a/zomp/lib/otpr/zx/0.2.0/src/zx_proxy.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_proxy.erl new file mode 100644 index 0000000..85dd297 --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_proxy.erl @@ -0,0 +1,250 @@ +%%% @doc +%%% ZX Proxy +%%% +%%% Abstraction to the local zx_daemon proxy. +%%% @end + +-module(zx_proxy). +-vsn("0.2.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0"). + +-export([subscribe/1, unsubscribe/1, request/1]). +-export([connect/1, disconnect/0]). +-export([start_link/0, init/1]). +-export([system_continue/3, system_terminate/4, + system_get_state/1, system_replace_state/2]). + +-include("zx_logger.hrl"). + +%%% Type and Record Definitions + +-record(s, {socket = none :: none | gen_tcp:socket()}). + +-type state() :: #s{}. + + +%%% Service Interface + +-spec subscribe(zx:package()) -> ok. + +subscribe(Realm) -> + ?MODULE ! {subscribe, Realm}, + ok. + + +-spec unsubscribe(zx:package()) -> ok. + +unsubscribe(Package) -> + ?MODULE ! {unsubscribe, Package}, + ok. + + +-spec request(Action) -> Result + when Action :: zx_daemon:action(), + Result :: term() + | {error, Reason :: timeout | term()}. + +request(Action) -> + Proxy = whereis(?MODULE), + Mon = monitor(process, Proxy), + Proxy ! {request, self(), Mon, Action}, + receive + {result, Mon, Result} -> + true = demonitor(Mon), + Result; + {'DOWN', Mon, process, Proxy, Info} -> + {error, Info} + after 5000 -> + {error, timeout} + end. + + +-spec connect(Port) -> Result + when Port :: inet:port_number(), + Result :: ok. + +connect(Port) -> + ?MODULE ! {connect, self(), Port}, + receive + {connect, Outcome} -> Outcome + after 5000 -> error + end. + + +-spec disconnect() -> ok. + +disconnect() -> + ?MODULE ! disconnect, + ok. + + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). + +start_link() -> + proc_lib:start_link(?MODULE, init, [self()]). + + +-spec init(Parent) -> no_return() + when Parent :: pid(). + +init(Parent) -> + Debug = sys:debug_options([]), + Self = self(), + true = register(?MODULE, Self), + ok = proc_lib:init_ack(Parent, {ok, Self}), + loop(Parent, Debug, #s{}). + + +-spec loop(Parent, Debug, State) -> no_return() + when Parent :: pid(), + Debug :: [sys:dbg_opt()], + State :: state(). + +loop(Parent, Debug, State = #s{socket = Socket}) -> + receive + {request, From, Ref, Action} -> + Result = dispatch(Socket, Action), + From ! {result, Ref, Result}, + loop(Parent, Debug, State); + {tcp, Socket, Bin} -> + ok = handle_message(Bin), + loop(Parent, Debug, State); + {connect, From, Port} -> + {Outcome, NewState} = connect(Port, State), + From ! {connect, Outcome}, + loop(Parent, Debug, NewState); + disconnect -> + NewState = disconnect(State), + loop(Parent, Debug, NewState); + {tcp_closed, Socket} -> + ok = log(info, "Socket closed."), + NewState = State#s{socket = none}, + loop(Parent, Debug, NewState); + {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. + + +dispatch(Socket, Action) -> + case Action of + {list, R} -> make_query(Socket, 3, R); + {list, R, N} -> make_query(Socket, 3, {R, N}); + {list, R, N, V} -> make_query(Socket, 3, {R, N, V}); + {latest, R, N} -> make_query(Socket, 4, {R, N}); + {latest, R, N, V} -> make_query(Socket, 4, {R, N, V}); + {provides, R, M} -> make_query(Socket, 5, {R, M}); + {list_deps, R, N, V} -> make_query(Socket, 6, {R, N, V}); + {list_sysops, R} -> make_query(Socket, 7, R); +% {fetch, R, N, V} -> fetch(Socket, {R, N, V}); + {fetch, R, N, V} -> make_query(Socket, 8, {R, N, V}); + {install, R, N, V} -> make_query(Socket, 9, {R, N, V}); + {build, R, N, V} -> make_query(Socket, 10, {R, N, V}); + {list_mirrors} -> make_query(Socket, 11, none); + {add_mirror, Host} -> make_query(Socket, 12, Host); + {drop_mirror, Host} -> make_query(Socket, 13, Host); + {register_key, Data} -> make_query(Socket, 14, Data); + {get_key, KeyID} -> make_query(Socket, 15, KeyID); + {keybin, KeyID} -> make_query(Socket, 16, KeyID); + {find_keypair, KeyID} -> make_query(Socket, 17, KeyID); + {have_key, Type, KID} -> make_query(Socket, 18, {Type, KID}); + {list_keys, R} -> make_query(Socket, 19, R); + {takeover, R} -> make_query(Socket, 20, R); + {abdicate, R} -> make_query(Socket, 21, R); + {drop_realm, R} -> make_query(Socket, 22, R); + {keychain, R, K} -> make_query(Socket, 23, {R, K}); + Unexpected -> + Message = "Received unexpected request action. Action: ~200tp", + ok = log(warning, Message, [Unexpected]), + {error, bad_message} + end. + + +make_query(Socket, Command, Payload) -> + Message = pack(Command, Payload), + ok = gen_tcp:send(Socket, Message), + receive + {tcp, Socket, <<0:8>>} -> ok; + {tcp, Socket, <<0:8, Bin/binary>>} -> zx_lib:b_to_t(Bin); + {tcp, Socket, Bin} -> {error, zx_net:err_in(Bin)} + after 5000 -> {error, timeout} + end. + + +pack(Command, none) -> <<0:1, Command:7>>; +pack(Command, Payload) -> <<0:1, Command:7, (term_to_binary(Payload))/binary>>. + + +handle_message(<<1:1, 1:7, Bin/binary>>) -> + {ok, {Channel, Message}} = zx_lib:b_to_ts(Bin), + zx_daemon:notify(Channel, Message). + + +connect(Port, State = #s{socket = none}) -> + Options = + [inet6, + {ip, {0,0,0,0,0,0,0,1}}, + {active, true}, + {mode, binary}, + {keepalive, true}, + {reuseaddr, true}, + {packet, 4}], + case gen_tcp:connect({0,0,0,0,0,0,0,1}, Port, Options) of + {ok, Socket} -> + {ok, State#s{socket = Socket}}; + {error, Reason} -> + ok = log(warning, "Connect to local proxy failed with ~tp.", [Reason]), + {error, State} + end. + + +disconnect(State = #s{socket = none}) -> + State; +disconnect(State = #s{socket = Socket}) -> + ok = zx_net:disconnect(Socket), + State#s{socket = none}. + + +%%% OTP System Message Handling + +-spec system_continue(Parent, Debug, State) -> no_return() + when Parent :: pid(), + Debug :: [sys:dbg_opt()], + State :: state(). + +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(). + +system_terminate(Reason, _Parent, _Debug, _State) -> + exit(Reason). + + +-spec system_get_state(State) -> {ok, State} + when State :: state(). + +system_get_state(State) -> {ok, State}. + + +-spec system_replace_state(StateFun, State) -> {ok, NewState, State} + when StateFun :: fun(), + State :: state(), + NewState :: term(). + +system_replace_state(StateFun, State) -> + {ok, StateFun(State), State}. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_sup.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_sup.erl similarity index 54% rename from zomp/lib/otpr/zx/0.1.0/src/zx_sup.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_sup.erl index 5f07fba..a7595cb 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_sup.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_sup.erl @@ -1,10 +1,11 @@ %%% @doc %%% ZX Daemon Supervisor %%% -%%% This supervisor maintains the lifecycle of the zxd worker process. +%%% This supervisor maintains the lifecycle of the zx_daemon worker process. %%% @end -module(zx_sup). +-vsn("0.2.0"). -behavior(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). @@ -42,19 +43,32 @@ start_link() -> init(none) -> RestartStrategy = {rest_for_one, 1, 60}, - Daemon = {zx_daemon, - {zx_daemon, start_link, []}, - permanent, - 10000, - worker, - [zx_daemon]}, + Daemon = {zx_daemon, + {zx_daemon, start_link, []}, + permanent, + 10000, + worker, + [zx_daemon]}, - ConnSup = {zx_conn_sup, - {zx_conn_sup, start_link, []}, - permanent, - brutal_kill, - supervisor, - [zx_conn_sup]}, + ConnSup = {zx_conn_sup, + {zx_conn_sup, start_link, []}, + permanent, + brutal_kill, + supervisor, + [zx_conn_sup]}, - Children = [Daemon, ConnSup], + Proxy = {zx_proxy, + {zx_proxy, start_link, []}, + permanent, + 5000, + worker, + [zx_proxy]}, + + PeerService = {zx_peers, + {zx_peers, start_link, []}, + permanent, + 5000, + supervisor, + [zx_peers]}, + Children = [Daemon, ConnSup, Proxy, PeerService], {ok, {RestartStrategy, Children}}. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_tty.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_tty.erl similarity index 97% rename from zomp/lib/otpr/zx/0.1.0/src/zx_tty.erl rename to zomp/lib/otpr/zx/0.2.0/src/zx_tty.erl index 8a7789b..8cd2c36 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_tty.erl +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_tty.erl @@ -6,6 +6,7 @@ %%% @end -module(zx_tty). +-vsn("0.2.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0"). @@ -182,7 +183,8 @@ hurr() -> io:format("That isn't an option.~n"). what_a_quitter() -> ok = io:format("User abort: \"QUIT\".~nHalting.~n"), - halt(0). + ok = init:stop(), + receive Message -> io:format("Death Note: ~tp~n", [Message]) end. -spec derp() -> ok. diff --git a/zomp/lib/otpr/zx/0.2.0/src/zx_zsp.erl b/zomp/lib/otpr/zx/0.2.0/src/zx_zsp.erl new file mode 100644 index 0000000..71e23c6 --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/src/zx_zsp.erl @@ -0,0 +1,273 @@ +%%% @doc +%%% ZX ZSP: The ZSP package interface +%%% +%%% ZSP files are project package files managed by Zomp and ZX. This module provides +%%% a common interface for interfacing with the contents of a ZSP file and a few helper +%%% functions. +%%% @end + +-module(zx_zsp). +-vsn("0.2.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0"). + +-export([pack/2, + unpack/1, blithely_unpack/1, + extract/2, blithely_extract/2, + verify/1, verify/2, + meta/1, package_id/1, + resign/2, resign/3]). + +-export_type([meta/0]). + +-include("zx_logger.hrl"). + + +-type meta() :: {PackageID :: zx:package_id(), + KeyName :: zx:key_name(), + Tags :: [string()], + Deps :: [zx:package_id()], + Modules :: [string()]}. + + +-spec pack(TargetDir, Key) -> Result + when TargetDir :: file:filename(), + Key :: public_key:rsa_public_key(), + Result :: ok + | {error, file:posix()}. + +pack(TargetDir, Key) -> + case zx_lib:read_project_meta(TargetDir) of + {ok, Meta} -> pack2(TargetDir, Key, Meta); + Error -> Error + end. + +pack2(TargetDir, Key, Meta) -> + PackageID = maps:get(package_id, Meta), + {ok, PackageString} = zx_lib:package_string(PackageID), + ZspFile = PackageString ++ ".zsp", + case filelib:is_regular(ZspFile) of + false -> pack3(TargetDir, PackageID, Meta, Key, ZspFile); + true -> {error, eexists} + end. + +pack3(TargetDir, PackageID, Meta, {KeyName, Key}, ZspFile) -> + Beams = filelib:wildcard("**/*.{beam,ez}", TargetDir), + ToDelete = [filename:join(TargetDir, Beam) || Beam <- Beams], + ok = lists:foreach(fun file:delete/1, ToDelete), + ok = zx_lib:rm_rf(filename:join(TargetDir, "erl_crash.dump")), + {ok, Everything} = file:list_dir(TargetDir), + DotFiles = filelib:wildcard(".*", TargetDir), + Targets = lists:subtract(Everything, DotFiles), + {ok, CWD} = file:get_cwd(), + ok = file:set_cwd(TargetDir), + ok = zx_local:update_app_file(), + Name = element(2, PackageID), + AppFile = filename:join("ebin", Name ++ ".app"), + {ok, [{application, _, AppData}]} = file:consult(AppFile), + Modules = lists:map(fun atom_to_list/1, proplists:get_value(modules, AppData)), + TarGzPath = filename:join(zx_lib:path(tmp), ZspFile ++ ".tgz"), + ok = erl_tar:create(TarGzPath, Targets, [compressed]), + {ok, TgzBin} = file:read_file(TarGzPath), + ok = file:delete(TarGzPath), + Tags = maps:get(tags, Meta, []), + Deps = maps:get(deps, Meta, []), + MetaBin = term_to_binary({PackageID, KeyName, Tags, Deps, Modules}), + MetaSize = byte_size(MetaBin), + SignMe = <>, + Sig = public_key:sign(SignMe, sha512, Key), + SigSize = byte_size(Sig), + ZspData = <>, + ok = file:set_cwd(CWD), + case file:write_file(ZspFile, ZspData) of + ok -> {ok, ZspFile}; + Error -> Error + end. + + +-spec unpack(ZspFile) -> Outcome + when ZspFile :: file:filename(), + Outcome :: ok + | {error, Reason}, + Reason :: bad_zsp + | bad_sig + | bad_key + | file:posix(). + +unpack(ZspFile) -> + case file:read_file(ZspFile) of + {ok, ZspBin} -> extract(ZspBin, cwd); + Error -> Error + end. + + +-spec blithely_unpack(ZspFile) -> Outcome + when ZspFile :: file:filename(), + Outcome :: ok + | {error, Reason}, + Reason :: bad_zsp + | file:posix(). + +blithely_unpack(ZspFile) -> + case file:read_file(ZspFile) of + {ok, ZspBin} -> blithely_extract(ZspBin, cwd); + Error -> Error + end. + + +-spec extract(ZspBin, Location) -> Outcome + when ZspBin :: binary(), + Location :: cwd + | lib, + Outcome :: ok + | {error, Reason}, + Reason :: bad_zsp + | bad_sig + | bad_key. + +extract(ZspBin, Location) -> + case verify(ZspBin) of + ok -> blithely_extract(ZspBin, Location); + Error -> Error + end. + + +blithely_extract(ZspBin, cwd) -> + {ok, Meta} = meta(ZspBin), + {ok, PackageString} = zx_lib:package_string(element(1, Meta)), + install(ZspBin, PackageString); +blithely_extract(ZspBin, lib) -> + {ok, Meta} = meta(ZspBin), + PackageID = element(1, Meta), + Path = zx_lib:ppath(lib, PackageID), + install(ZspBin, Path). + + +install(<>, Path) -> + ok = filelib:ensure_dir(Path), + ok = zx_lib:rm_rf(Path), + ok = file:make_dir(Path), + erl_tar:extract({binary, TarGZ}, [{cwd, Path}, compressed]). + + +-spec verify(ZspBin) -> Outcome + when ZspBin :: binary(), + Outcome :: ok + | {error, Reason}, + Reason :: bad_zsp + | bad_sig + | bad_key. + +verify(<>) -> + verify2(Sig, Signed); +verify(_) -> + {error, bad_zsp}. + +verify2(Sig, Signed = <>) -> + case zx_lib:b_to_ts(MetaBin) of + {ok, {{Realm, _, _}, SigKeyName, _, _, _}} -> + SigKeyID = {Realm, SigKeyName}, + verify3(Sig, Signed, SigKeyID); + error -> + {error, bad_zsp} + end; +verify2(_, _) -> + {error, bad_zsp}. + +verify3(Sig, Signed, SigKeyID) -> + case zx_key:load(public, SigKeyID) of + {ok, PubKey} -> + verify4(Signed, Sig, PubKey); + {error, Reason} -> + Message = "zx_key:load(public, ~tp) failed with: ~tp", + ok = log(warning, Message, [SigKeyID, Reason]), + {error, bad_key} + end. + +verify4(Signed, Sig, PubKey) -> + case zx_key:verify(Signed, Sig, PubKey) of + true -> ok; + false -> {error, bad_sig} + end. + + +-spec verify(ZspBin, PubKey) -> boolean() + when ZspBin :: binary(), + PubKey :: public_key:rsa_public_key(). + +verify(<>, PubKey) -> + zx_key:verify(Signed, Sig, PubKey). + + +-spec meta(binary()) -> {ok, meta()} | {error, bad_zsp}. + +meta(<>) -> + case zx_lib:b_to_ts(MetaBin) of + {ok, Meta = {_, _, _, _, _}} -> {ok, Meta}; + _ -> {error, bad_zsp} + end. + + +-spec package_id(binary()) -> {ok, zx:package_id()} | {error, bad_zsp}. + +package_id(Bin) -> + case meta(Bin) of + {ok, Meta} -> {ok, element(1, Meta)}; + Error -> Error + end. + + +-spec resign(KeyID, ZspBin) -> Outcome + when KeyID :: zx:key_id(), + ZspBin :: binary(), + Outcome :: {ok, binary()} + | {error, Reason}, + Reason :: bad_zsp + | bad_realm + | no_key + | bad_key. + +resign(KeyID = {Realm, KeyName}, + <>) -> + case zx_daemon:get_key(private, KeyID) of + {ok, Key} -> resign2(Realm, KeyName, Key, MetaBin, TarGZ); + Error -> Error + end; +resign(_, _) -> + {error, bad_zsp}. + + +-spec resign(KeyID, Key, ZspBin) -> Outcome + when KeyID :: zx:key_id(), + Key :: public_key:rsa_private_key(), + ZspBin :: binary(), + Outcome :: {ok, binary()} + | {error, Reason}, + Reason :: bad_zsp + | bad_realm + | no_key + | bad_key. + +resign({Realm, KeyName}, + Key, + <>) -> + resign2(Realm, KeyName, Key, MetaBin, TarGZ); +resign(_, _, _) -> + {error, bad_zsp}. + +resign2(Realm, KeyName, Key, MetaBin, TarGZ) -> + case zx_lib:b_to_ts(MetaBin) of + {ok, Meta = {{Realm, _, _}, _, _, _, _}} -> resign3(KeyName, Key, Meta, TarGZ); + {ok, _} -> {error, bad_realm}; + error -> {error, bad_zsp} + end. + +resign3(KeyName, Key, Meta, TarGZ) -> + MetaBin = term_to_binary(setelement(2, Meta, KeyName)), + MetaSize = byte_size(MetaBin), + SignMe = <>, + Sig = public_key:sign(SignMe, sha512, Key), + SigSize = byte_size(Sig), + ZspBin = <>, + {ok, ZspBin}. diff --git a/zomp/lib/otpr/zx/0.1.0/templates/Emakefile b/zomp/lib/otpr/zx/0.2.0/templates/Emakefile similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/Emakefile rename to zomp/lib/otpr/zx/0.2.0/templates/Emakefile diff --git a/zomp/lib/otpr/zx/0.1.0/templates/boringlib/funfile.erl b/zomp/lib/otpr/zx/0.2.0/templates/boringlib/funfile.erl similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/boringlib/funfile.erl rename to zomp/lib/otpr/zx/0.2.0/templates/boringlib/funfile.erl diff --git a/zomp/lib/otpr/zx/0.1.0/templates/escript b/zomp/lib/otpr/zx/0.2.0/templates/escript similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/escript rename to zomp/lib/otpr/zx/0.2.0/templates/escript diff --git a/zomp/lib/otpr/zx/0.1.0/templates/example_server/appmod.erl b/zomp/lib/otpr/zx/0.2.0/templates/example_server/appmod.erl similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/example_server/appmod.erl rename to zomp/lib/otpr/zx/0.2.0/templates/example_server/appmod.erl diff --git a/zomp/lib/otpr/zx/0.1.0/templates/example_server/client.erl b/zomp/lib/otpr/zx/0.2.0/templates/example_server/client.erl similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/example_server/client.erl rename to zomp/lib/otpr/zx/0.2.0/templates/example_server/client.erl diff --git a/zomp/lib/otpr/zx/0.1.0/templates/example_server/client_man.erl b/zomp/lib/otpr/zx/0.2.0/templates/example_server/client_man.erl similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/example_server/client_man.erl rename to zomp/lib/otpr/zx/0.2.0/templates/example_server/client_man.erl diff --git a/zomp/lib/otpr/zx/0.1.0/templates/example_server/client_sup.erl b/zomp/lib/otpr/zx/0.2.0/templates/example_server/client_sup.erl similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/example_server/client_sup.erl rename to zomp/lib/otpr/zx/0.2.0/templates/example_server/client_sup.erl diff --git a/zomp/lib/otpr/zx/0.1.0/templates/example_server/clients.erl b/zomp/lib/otpr/zx/0.2.0/templates/example_server/clients.erl similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/example_server/clients.erl rename to zomp/lib/otpr/zx/0.2.0/templates/example_server/clients.erl diff --git a/zomp/lib/otpr/zx/0.1.0/templates/example_server/sup.erl b/zomp/lib/otpr/zx/0.2.0/templates/example_server/sup.erl similarity index 100% rename from zomp/lib/otpr/zx/0.1.0/templates/example_server/sup.erl rename to zomp/lib/otpr/zx/0.2.0/templates/example_server/sup.erl diff --git a/zomp/lib/otpr/zx/0.2.0/zomp.meta b/zomp/lib/otpr/zx/0.2.0/zomp.meta new file mode 100644 index 0000000..52ee53e --- /dev/null +++ b/zomp/lib/otpr/zx/0.2.0/zomp.meta @@ -0,0 +1,5 @@ +{deps,[]}. +{package_id,{"otpr","zx",{0,2,0}}}. +{prefix,"zx_"}. +{tags,[]}. +{type,app}. diff --git a/zomp/zx b/zomp/zx index 154149f..8be2164 100755 --- a/zomp/zx +++ b/zomp/zx @@ -1,11 +1,12 @@ -#!/bin/sh +#!/bin/bash -export ZOMP_DIR="${ZOMP_DIR:-$HOME/.zomp}" -version=$(cat "$ZOMP_DIR/etc/version.txt") -export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$version" +. "$HOME"/.bash_profile +export ZOMP_DIR="${ZOMP_DIR:-$HOME/zomp}" +export ZX_VERSION=$(cat "$ZOMP_DIR/etc/version.txt") +export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$ZX_VERSION" start_dir="$PWD" cd "$ZX_DIR" ./make_zx cd "$start_dir" -erl -noshell -pa "$ZX_DIR/ebin" -run zx do $@ +erl -noshell -pa "$ZX_DIR/ebin" -run zx do -extra $@ diff --git a/zomp/zxh b/zomp/zxh index 5b262bf..4b11a00 100755 --- a/zomp/zxh +++ b/zomp/zxh @@ -1,11 +1,12 @@ #!/bin/sh -export ZOMP_DIR="${ZOMP_DIR:-$HOME/.zomp}" -version=$(cat "$ZOMP_DIR/etc/version.txt") -export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$version" +. "$HOME"/.bash_profile +export ZOMP_DIR="${ZOMP_DIR:-$HOME/zomp}" +export ZX_VERSION=$(cat "$ZOMP_DIR/etc/version.txt") +export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$ZX_VERSION" start_dir="$PWD" cd "$ZX_DIR" ./make_zx cd "$start_dir" -erl -pa "$ZX_DIR/ebin" -run zx do $@ +erl -pa "$ZX_DIR/ebin" -run zx do -extra $@