2015-03-31 07:46:03 +00:00
|
|
|
%% -------------------------------------------------------------------
|
|
|
|
%%
|
|
|
|
%% Copyright (c) 2007-2015 Basho Technologies, Inc. All Rights Reserved.
|
|
|
|
%%
|
|
|
|
%% This file is provided to you under the Apache License,
|
|
|
|
%% Version 2.0 (the "License"); you may not use this file
|
|
|
|
%% except in compliance with the License. You may obtain
|
|
|
|
%% a copy of the License at
|
|
|
|
%%
|
|
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
%%
|
|
|
|
%% Unless required by applicable law or agreed to in writing,
|
|
|
|
%% software distributed under the License is distributed on an
|
|
|
|
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
%% KIND, either express or implied. See the License for the
|
|
|
|
%% specific language governing permissions and limitations
|
|
|
|
%% under the License.
|
|
|
|
%%
|
|
|
|
%% -------------------------------------------------------------------
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Miscellaneous utility functions.
|
|
|
|
|
2015-03-31 07:46:03 +00:00
|
|
|
-module(machi_util).
|
|
|
|
|
|
|
|
-export([
|
2015-04-08 05:24:07 +00:00
|
|
|
checksum_chunk/1,
|
2015-06-23 05:08:10 +00:00
|
|
|
make_tagged_csum/1, make_tagged_csum/2,
|
2015-06-26 11:47:55 +00:00
|
|
|
unmake_tagged_csum/1,
|
2015-03-31 07:46:03 +00:00
|
|
|
hexstr_to_bin/1, bin_to_hexstr/1,
|
|
|
|
hexstr_to_int/1, int_to_hexstr/2, int_to_hexbin/2,
|
2015-04-02 03:38:12 +00:00
|
|
|
make_binary/1, make_string/1,
|
2015-03-31 07:46:03 +00:00
|
|
|
make_regname/1,
|
2015-04-08 09:39:55 +00:00
|
|
|
make_config_filename/2,
|
|
|
|
make_checksum_filename/4, make_checksum_filename/2,
|
|
|
|
make_data_filename/4, make_data_filename/2,
|
2015-04-03 08:10:52 +00:00
|
|
|
make_projection_filename/2,
|
2015-03-31 07:46:03 +00:00
|
|
|
read_max_filenum/2, increment_max_filenum/2,
|
|
|
|
info_msg/2, verb/1, verb/2,
|
2015-05-20 08:30:37 +00:00
|
|
|
mbytes/1,
|
2015-03-31 07:46:03 +00:00
|
|
|
%% TCP protocol helpers
|
2015-07-03 10:21:41 +00:00
|
|
|
connect/2, connect/3,
|
|
|
|
%% List twiddling
|
|
|
|
permutations/1, perms/1,
|
|
|
|
combinations/1, ordered_combinations/1,
|
|
|
|
mk_order/2
|
2015-03-31 07:46:03 +00:00
|
|
|
]).
|
|
|
|
-compile(export_all).
|
|
|
|
|
|
|
|
-include("machi.hrl").
|
|
|
|
-include("machi_projection.hrl").
|
|
|
|
-include_lib("kernel/include/file.hrl").
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Create a registered name atom for FLU sequencer internal
|
|
|
|
%% rendezvous/message passing use.
|
2015-03-31 07:46:03 +00:00
|
|
|
|
2015-06-19 07:04:34 +00:00
|
|
|
-spec make_regname(binary()|string()) ->
|
2015-04-08 09:39:55 +00:00
|
|
|
atom().
|
2015-03-31 07:46:03 +00:00
|
|
|
make_regname(Prefix) when is_binary(Prefix) ->
|
|
|
|
erlang:binary_to_atom(Prefix, latin1);
|
|
|
|
make_regname(Prefix) when is_list(Prefix) ->
|
|
|
|
erlang:list_to_atom(Prefix).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Calculate a config file path, by common convention.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_config_filename(string(), string()) ->
|
|
|
|
string().
|
2015-03-31 07:46:03 +00:00
|
|
|
make_config_filename(DataDir, Prefix) ->
|
|
|
|
lists:flatten(io_lib:format("~s/config/~s", [DataDir, Prefix])).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Calculate a checksum file path, by common convention.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_checksum_filename(string(), string(), atom()|string()|binary(), integer()) ->
|
|
|
|
string().
|
2015-03-31 07:46:03 +00:00
|
|
|
make_checksum_filename(DataDir, Prefix, SequencerName, FileNum) ->
|
|
|
|
lists:flatten(io_lib:format("~s/config/~s.~s.~w.csum",
|
|
|
|
[DataDir, Prefix, SequencerName, FileNum])).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Calculate a checksum file path, by common convention.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_checksum_filename(string(), [] | string() | binary()) ->
|
|
|
|
string().
|
2015-04-03 08:10:52 +00:00
|
|
|
make_checksum_filename(DataDir, "") ->
|
|
|
|
lists:flatten(io_lib:format("~s/config", [DataDir]));
|
2015-03-31 07:46:03 +00:00
|
|
|
make_checksum_filename(DataDir, FileName) ->
|
|
|
|
lists:flatten(io_lib:format("~s/config/~s.csum", [DataDir, FileName])).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Calculate a file data file path, by common convention.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_data_filename(string(), string(), atom()|string()|binary(), integer()) ->
|
|
|
|
{binary(), string()}.
|
|
|
|
make_data_filename(DataDir, Prefix, SequencerName, FileNum) ->
|
|
|
|
File = erlang:iolist_to_binary(io_lib:format("~s.~s.~w",
|
|
|
|
[Prefix, SequencerName, FileNum])),
|
2015-04-03 08:10:52 +00:00
|
|
|
FullPath = lists:flatten(io_lib:format("~s/data/~s", [DataDir, File])),
|
2015-03-31 07:46:03 +00:00
|
|
|
{File, FullPath}.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Calculate a file data file path, by common convention.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_data_filename(string(), [] | string() | binary()) ->
|
|
|
|
{binary(), string()}.
|
|
|
|
make_data_filename(DataDir, "") ->
|
|
|
|
FullPath = lists:flatten(io_lib:format("~s/data", [DataDir])),
|
|
|
|
{"", FullPath};
|
|
|
|
make_data_filename(DataDir, File) ->
|
2015-04-03 08:10:52 +00:00
|
|
|
FullPath = lists:flatten(io_lib:format("~s/data/~s", [DataDir, File])),
|
2015-03-31 07:46:03 +00:00
|
|
|
{File, FullPath}.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Calculate a projection store file path, by common convention.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_projection_filename(string(), [] | string()) ->
|
|
|
|
string().
|
2015-04-03 08:10:52 +00:00
|
|
|
make_projection_filename(DataDir, "") ->
|
|
|
|
lists:flatten(io_lib:format("~s/projection", [DataDir]));
|
|
|
|
make_projection_filename(DataDir, File) ->
|
|
|
|
lists:flatten(io_lib:format("~s/projection/~s", [DataDir, File])).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Read the file size of a config file, which is used as the
|
|
|
|
%% basis for a minimum sequence number.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec read_max_filenum(string(), string()) ->
|
|
|
|
non_neg_integer().
|
2015-03-31 07:46:03 +00:00
|
|
|
read_max_filenum(DataDir, Prefix) ->
|
|
|
|
case file:read_file_info(make_config_filename(DataDir, Prefix)) of
|
|
|
|
{error, enoent} ->
|
|
|
|
0;
|
|
|
|
{ok, FI} ->
|
|
|
|
FI#file_info.size
|
|
|
|
end.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Increase the file size of a config file, which is used as the
|
|
|
|
%% basis for a minimum sequence number.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec increment_max_filenum(string(), string()) ->
|
|
|
|
ok | {error, term()}.
|
2015-03-31 07:46:03 +00:00
|
|
|
increment_max_filenum(DataDir, Prefix) ->
|
|
|
|
try
|
|
|
|
{ok, FH} = file:open(make_config_filename(DataDir, Prefix), [append]),
|
|
|
|
ok = file:write(FH, "x"),
|
|
|
|
%% ok = file:sync(FH),
|
|
|
|
ok = file:close(FH)
|
|
|
|
catch
|
|
|
|
error:{badmatch,_}=Error ->
|
2015-04-08 09:39:55 +00:00
|
|
|
{error, {Error, erlang:get_stacktrace()}}
|
2015-03-31 07:46:03 +00:00
|
|
|
end.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Convert a hexadecimal string to a `binary()'.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec hexstr_to_bin(string() | binary()) ->
|
|
|
|
binary().
|
2015-03-31 07:46:03 +00:00
|
|
|
hexstr_to_bin(S) when is_list(S) ->
|
|
|
|
hexstr_to_bin(S, []);
|
|
|
|
hexstr_to_bin(B) when is_binary(B) ->
|
|
|
|
hexstr_to_bin(binary_to_list(B), []).
|
|
|
|
|
|
|
|
hexstr_to_bin([], Acc) ->
|
|
|
|
list_to_binary(lists:reverse(Acc));
|
|
|
|
hexstr_to_bin([X,Y|T], Acc) ->
|
|
|
|
{ok, [V], []} = io_lib:fread("~16u", [X,Y]),
|
|
|
|
hexstr_to_bin(T, [V | Acc]).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Convert a `binary()' to a hexadecimal string.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec bin_to_hexstr(binary()) ->
|
|
|
|
string().
|
2015-03-31 07:46:03 +00:00
|
|
|
bin_to_hexstr(<<>>) ->
|
|
|
|
[];
|
|
|
|
bin_to_hexstr(<<X:4, Y:4, Rest/binary>>) ->
|
|
|
|
[hex_digit(X), hex_digit(Y)|bin_to_hexstr(Rest)].
|
|
|
|
|
|
|
|
hex_digit(X) when X < 10 ->
|
|
|
|
X + $0;
|
|
|
|
hex_digit(X) ->
|
|
|
|
X - 10 + $a.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Convert a compatible Erlang data type into a `binary()' equivalent.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_binary(binary() | iolist()) ->
|
|
|
|
binary().
|
2015-03-31 07:46:03 +00:00
|
|
|
make_binary(X) when is_binary(X) ->
|
|
|
|
X;
|
|
|
|
make_binary(X) when is_list(X) ->
|
|
|
|
iolist_to_binary(X).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Convert a compatible Erlang data type into a `string()' equivalent.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec make_string(binary() | iolist()) ->
|
|
|
|
string().
|
2015-04-02 03:38:12 +00:00
|
|
|
make_string(X) when is_list(X) ->
|
|
|
|
lists:flatten(X);
|
|
|
|
make_string(X) when is_binary(X) ->
|
|
|
|
binary_to_list(X).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Convert a hexadecimal string to an integer.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec hexstr_to_int(string() | binary()) ->
|
|
|
|
non_neg_integer().
|
2015-03-31 07:46:03 +00:00
|
|
|
hexstr_to_int(X) ->
|
|
|
|
B = hexstr_to_bin(X),
|
|
|
|
B_size = byte_size(B) * 8,
|
|
|
|
<<I:B_size/big>> = B,
|
|
|
|
I.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Convert an integer into a hexadecimal string whose length is
|
|
|
|
%% based on `I_size'.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec int_to_hexstr(non_neg_integer(), non_neg_integer()) ->
|
|
|
|
string().
|
2015-03-31 07:46:03 +00:00
|
|
|
int_to_hexstr(I, I_size) ->
|
|
|
|
bin_to_hexstr(<<I:I_size/big>>).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Convert an integer into a hexadecimal string (in `binary()'
|
|
|
|
%% form) whose length is based on `I_size'.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec int_to_hexbin(non_neg_integer(), non_neg_integer()) ->
|
|
|
|
binary().
|
2015-03-31 07:46:03 +00:00
|
|
|
int_to_hexbin(I, I_size) ->
|
|
|
|
list_to_binary(int_to_hexstr(I, I_size)).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Calculate a checksum for a chunk of file data.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec checksum_chunk(binary() | iolist()) ->
|
|
|
|
binary().
|
2015-04-08 05:24:07 +00:00
|
|
|
checksum_chunk(Chunk) when is_binary(Chunk); is_list(Chunk) ->
|
|
|
|
crypto:hash(sha, Chunk).
|
|
|
|
|
2015-06-02 04:13:12 +00:00
|
|
|
%% @doc Create a tagged checksum
|
|
|
|
|
2015-06-23 05:08:10 +00:00
|
|
|
make_tagged_csum(none) ->
|
|
|
|
<<?CSUM_TAG_NONE:8>>;
|
|
|
|
make_tagged_csum({Tag, CSum}) ->
|
|
|
|
make_tagged_csum(Tag, CSum).
|
|
|
|
|
|
|
|
make_tagged_csum(none, _SHA) ->
|
|
|
|
<<?CSUM_TAG_NONE:8>>;
|
|
|
|
make_tagged_csum(client_sha, SHA) ->
|
|
|
|
<<?CSUM_TAG_CLIENT_SHA:8, SHA/binary>>;
|
|
|
|
make_tagged_csum(server_sha, SHA) ->
|
|
|
|
<<?CSUM_TAG_SERVER_SHA:8, SHA/binary>>;
|
|
|
|
make_tagged_csum(server_regen_sha, SHA) ->
|
|
|
|
<<?CSUM_TAG_SERVER_REGEN_SHA:8, SHA/binary>>.
|
2015-06-02 04:13:12 +00:00
|
|
|
|
2015-06-26 09:22:15 +00:00
|
|
|
unmake_tagged_csum(<<Tag:8, Rest/binary>>) ->
|
|
|
|
{Tag, Rest}.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Log a verbose message.
|
2015-03-31 07:46:03 +00:00
|
|
|
|
2015-06-19 07:04:34 +00:00
|
|
|
-spec verb(string()) -> ok.
|
2015-03-31 07:46:03 +00:00
|
|
|
verb(Fmt) ->
|
|
|
|
verb(Fmt, []).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Log a verbose message.
|
|
|
|
|
2015-06-19 07:04:34 +00:00
|
|
|
-spec verb(string(), list()) -> ok.
|
2015-03-31 07:46:03 +00:00
|
|
|
verb(Fmt, Args) ->
|
|
|
|
case application:get_env(kernel, verbose) of
|
|
|
|
{ok, true} -> io:format(Fmt, Args);
|
|
|
|
_ -> ok
|
|
|
|
end.
|
|
|
|
|
2015-05-20 08:30:37 +00:00
|
|
|
mbytes(0) ->
|
|
|
|
"0.0";
|
|
|
|
mbytes(Size) ->
|
|
|
|
lists:flatten(io_lib:format("~.1.0f", [max(0.1, Size / (1024*1024))])).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Log an 'info' level message.
|
|
|
|
|
2015-04-08 09:39:55 +00:00
|
|
|
-spec info_msg(string(), list()) -> term().
|
2015-03-31 07:46:03 +00:00
|
|
|
info_msg(Fmt, Args) ->
|
|
|
|
case application:get_env(kernel, verbose) of {ok, false} -> ok;
|
|
|
|
_ -> error_logger:info_msg(Fmt, Args)
|
|
|
|
end.
|
|
|
|
|
2015-05-01 05:51:42 +00:00
|
|
|
wait_for_death(Pid, 0) ->
|
|
|
|
exit({not_dead_yet, Pid});
|
|
|
|
wait_for_death(Pid, Iters) when is_pid(Pid) ->
|
|
|
|
case erlang:is_process_alive(Pid) of
|
|
|
|
false ->
|
|
|
|
ok;
|
|
|
|
true ->
|
|
|
|
timer:sleep(1),
|
|
|
|
wait_for_death(Pid, Iters-1)
|
|
|
|
end.
|
|
|
|
|
2015-03-31 07:46:03 +00:00
|
|
|
%%%%%%%%%%%%%%%%%
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Create a TCP connection to a remote Machi server.
|
|
|
|
|
2015-04-02 05:17:57 +00:00
|
|
|
-spec connect(inet:ip_address() | inet:hostname(), inet:port_number()) ->
|
|
|
|
port().
|
|
|
|
connect(Host, Port) ->
|
2015-04-06 09:43:52 +00:00
|
|
|
escript_connect(Host, Port, 4500).
|
2015-03-31 07:46:03 +00:00
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Create a TCP connection to a remote Machi server.
|
|
|
|
|
2015-04-06 09:43:52 +00:00
|
|
|
-spec connect(inet:ip_address() | inet:hostname(), inet:port_number(),
|
|
|
|
timeout()) ->
|
|
|
|
port().
|
|
|
|
connect(Host, Port, Timeout) ->
|
|
|
|
escript_connect(Host, Port, Timeout).
|
|
|
|
|
|
|
|
escript_connect(Host, PortStr, Timeout) when is_list(PortStr) ->
|
2015-04-02 05:17:57 +00:00
|
|
|
Port = list_to_integer(PortStr),
|
2015-04-06 09:43:52 +00:00
|
|
|
escript_connect(Host, Port, Timeout);
|
|
|
|
escript_connect(Host, Port, Timeout) when is_integer(Port) ->
|
2015-04-02 05:17:57 +00:00
|
|
|
{ok, Sock} = gen_tcp:connect(Host, Port, [{active,false}, {mode,binary},
|
2015-04-06 09:43:52 +00:00
|
|
|
{packet, raw}], Timeout),
|
2015-04-02 05:17:57 +00:00
|
|
|
Sock.
|
2015-07-03 10:21:41 +00:00
|
|
|
|
|
|
|
permutations(L) ->
|
|
|
|
perms(L).
|
|
|
|
|
|
|
|
perms([]) -> [[]];
|
|
|
|
perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])].
|
|
|
|
|
|
|
|
combinations(L) ->
|
|
|
|
lists:usort(perms(L) ++ lists:append([ combinations(L -- [X]) || X <- L])).
|
|
|
|
|
|
|
|
ordered_combinations(Master) ->
|
|
|
|
[L || L <- combinations(Master), is_ordered(L, Master)].
|
|
|
|
|
|
|
|
is_ordered(L, Reference) ->
|
|
|
|
L_order = mk_order(L, Reference),
|
|
|
|
lists:all(fun(X) -> is_integer(X) end, L_order) andalso
|
|
|
|
L_order == lists:sort(L_order).
|
|
|
|
|
|
|
|
mk_order(UPI2, Repair1) ->
|
|
|
|
R1 = length(Repair1),
|
|
|
|
Repair1_order_d = orddict:from_list(lists:zip(Repair1, lists:seq(1, R1))),
|
|
|
|
UPI2_order = [case orddict:find(X, Repair1_order_d) of
|
|
|
|
{ok, Idx} -> Idx;
|
|
|
|
error -> error
|
|
|
|
end || X <- UPI2],
|
|
|
|
UPI2_order.
|