aboutsummaryrefslogblamecommitdiff
path: root/src/ejabberd_ctl.erl
blob: f0a1fd7486c307e2034441f6870cd80fa4948bc0 (plain) (tree)
1
2
3
4
5
6
7
8

                                                                         
                                                      
                                              


                                                                     
                                                  









                                                                      
   


                                                                           
   

                                                                         
                                                                          
 
                      
 
                       
                                  
 
                                                        


                                                             
 
                             
                                  
                       
                                    
 

                                  
                   




                               
          
                      



                                                                    
                                                            
                                     
                                                                   
















                                                                                
                                                                                   




                                                                        


                                                                    



                      
 



                                                             

                   


                                                                 
 

                                               

                     

                                                



                             
 

                                      
 
                               


                               
                                               




                                                                  
 

                                                                   
                                
                                                         
                                                
                                                     
                                                                          
                
                                                             
                                                          
                                                    
                                                                             
                          
               
                                                                                         

                           
 
                              
                            

                    
 
                                 

                    
 


                                                                               
                                
                                             

                    
                                        


                    
                                     
                                                       

                                                         


                    

                                                                         
                                    


                                      

                                           
                     
                                                     

                          
                                                     
                          
                   
                                                    
                            

                                                     
                            




                                                                
                            











                                                                             
                           

        
                                        
             







                                             
                                                 


                  
                                        
        

         
                                                                                  



                                                                                           



                                                                                 
                                                    

 
 

                                                            
                                         
                                 

                                                              
                                                         

                                                   
                                                      

                                          
                                 

                                                     
                                  




                                                                                 














                                                                                                        



                               
                                                                                                          
                                                   

                                                                       
                                                                  
                
                                 
                                
                 


                        
                                 


                                                                                       
                                         
                            
                                                                                                  
        
 
                                                                                                          

                                                            

                                            


               



                                                                                             
                                                                   

                                                                           

                                                          
                                  
                                          
                                                                                         

                                                                           

        
                                                                                                                               
                                                                   


                                                                
                                                                                                 

















                                                                                

                                                                       

                                                             















                                                     

                           
                          
                                            


                          
                                            



                                   


                                                                 



                               


                                                                  


                                                                               

                                                                          
 





                                       
                                                                                      
                                   
 
                                                                
                                                   
 
                                                          
                                               

                                                                   
                                                     



                                         



                                                 
                                                      

























                                                                                



                                 


                                     

                                                                     


                                      
 

                                                   











                                                                            
                                          
                                                                     












                                                              
                                           


            



                               
                    
                     






                                                                    
 
                     

                      





                                                                    
 
                       
                                      

                                               




                                               
                                                                        
                                   
 
          

                                                                                                                       

                                                            
                                                              



























                                                                                                 
                             












                                                  
 







                                                                         

                                                                   







                                                       




















                                                                              


















                                                        
 




                                                                                    





                                                                      



                                                                     

                                                              





                                                             


                                                                    






                               
                                          
                                                      
                                                                

                                            
                                                       

                                                       
                                 

                    
                      
 





















                                                                                      
                                               
                                                                          
                    
                                                                





                                                                 
                                                                          
                                                                      






                                                                   
                    







                                 


                                                                                                                     
                                    






                                                                                    

                                  





                                                                 
              
                                                                         
                                                                           
                                                                                      

                           





                                                   





                               
                                                                                    
                                                             
                                                   
                                                                                                        

                                                           
                                                                                      
                                                                           
        
 

                                                      
                                                     












                                                                                                               





                                                       
 
                                    
                                                             

                     
                                                                          
                          
                           
                            




                           
                                                                           
                                                  
                             

                                                                
 
                                             


                                      
                                    
                                         

                                             
                                                              















                                                                                                     
                                                                                                               
 
                                                                                







                                                                               
                                                                                                                                      

                          





                                                                             
 
                                      
                                                                                                                   

                                
                                              
                                                                                                                   






















                                                                                    

                                           
 






                                           
%%%----------------------------------------------------------------------
%%% File    : ejabberd_ctl.erl
%%% Author  : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : ejabberd command line admin tool
%%% Created : 11 Jan 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2021   ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------

%%% Does not support commands that have arguments with ctypes: list, tuple

-module(ejabberd_ctl).

-behaviour(gen_server).
-author('alexey@process-one.net').

-export([start/0, start_link/0, process/1, process2/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").

-define(DEFAULT_VERSION, 1000000).

-record(state, {}).

%%-----------------------------
%% Module
%%-----------------------------

start() ->
    disable_logging(),
    [SNode, Timeout, Args] = case init:get_plain_arguments() of
                                 [SNode2, "--no-timeout" | Args2] ->
                                     [SNode2, infinity, Args2];
                                 [SNode3 | Args3] ->
                                     [SNode3, 60000, Args3];
                                 _ ->
                                     print_usage(?DEFAULT_VERSION),
                                     halt(?STATUS_USAGE)
                             end,
    SNode1 = case string:tokens(SNode, "@") of
                 [_Node, _Server] ->
                     SNode;
                 _ ->
                     case net_kernel:longnames() of
                         true ->
                             lists:flatten([SNode, "@", inet_db:gethostname(),
                                            ".", inet_db:res_option(domain)]);
                         false ->
                             lists:flatten([SNode, "@", inet_db:gethostname()]);
                         _ ->
                             SNode
                     end
             end,
    Node = list_to_atom(SNode1),
    Status = case ejabberd_cluster:call(Node, ?MODULE, process, [Args], Timeout) of
                 {badrpc, Reason} ->
                     print("Failed RPC connection to the node ~p: ~p~n",
                           [Node, Reason]),
                     %% TODO: show minimal start help
                     ?STATUS_BADRPC;
                 {invalid_version, V} ->
                     print("Invalid API version number: ~p~n", [V]),
                     ?STATUS_ERROR;
                 S ->
                     S
             end,
    halt(Status).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    {ok, #state{}}.

handle_call(Request, From, State) ->
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
    {noreply, State}.

handle_cast(Msg, State) ->
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
    {noreply, State}.

handle_info(Info, State) ->
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%-----------------------------
%% Process
%%-----------------------------

-spec process([string()]) -> non_neg_integer().
process(Args) ->
    process(Args, ?DEFAULT_VERSION).


-spec process([string()], non_neg_integer()) -> non_neg_integer().

%% The commands status, stop and restart are defined here to ensure
%% they are usable even if ejabberd is completely stopped.
process(["status"], _Version) ->
    {InternalStatus, ProvidedStatus} = init:get_status(),
    print("The node ~p is ~p with status: ~p~n",
	   [node(), InternalStatus, ProvidedStatus]),
    case lists:keymember(ejabberd, 1, application:which_applications()) of
        false ->
            EjabberdLogPath = ejabberd_logger:get_log_path(),
            print("ejabberd is not running in that node~n"
		   "Check for error messages: ~ts~n"
		   "or other files in that directory.~n", [EjabberdLogPath]),
            ?STATUS_ERROR;
        true ->
            print("ejabberd ~ts is running in that node~n", [ejabberd_option:version()]),
            ?STATUS_SUCCESS
    end;

process(["stop"], _Version) ->
    %%ejabberd_cover:stop(),
    init:stop(),
    ?STATUS_SUCCESS;

process(["restart"], _Version) ->
    init:restart(),
    ?STATUS_SUCCESS;

%% TODO: Mnesia operations should not be hardcoded in ejabberd_ctl module.
%% For now, I leave them there to avoid breaking those commands for people that
%% may be using it (as format of response is going to change).
process(["mnesia"], _Version) ->
    print("~p~n", [mnesia:system_info(all)]),
    ?STATUS_SUCCESS;

process(["mnesia", "info"], _Version) ->
    mnesia:info(),
    ?STATUS_SUCCESS;

process(["mnesia", Arg], _Version) ->
    case catch mnesia:system_info(list_to_atom(Arg)) of
	{'EXIT', Error} -> print("Error: ~p~n", [Error]);
	Return -> print("~p~n", [Return])
    end,
    ?STATUS_SUCCESS;

%% The arguments --long and --dual are not documented because they are
%% automatically selected depending in the number of columns of the shell
process(["help" | Mode], Version) ->
    {MaxC, ShCode} = get_shell_info(),
    case Mode of
	[] ->
	    print_usage_help(MaxC, ShCode),
	    ?STATUS_SUCCESS;
	["--dual"] ->
	    print_usage(dual, MaxC, ShCode, Version),
	    ?STATUS_USAGE;
	["--long"] ->
	    print_usage(long, MaxC, ShCode, Version),
	    ?STATUS_USAGE;
	["tags"] ->
	    print_usage_tags(MaxC, ShCode, Version),
	    ?STATUS_SUCCESS;
	["--tags"] -> % deprecated in favor of "tags"
	    print_usage_tags(MaxC, ShCode, Version),
	    ?STATUS_SUCCESS;
	["commands"] ->
	    print_usage_tags_long(MaxC, ShCode, Version),
	    ?STATUS_SUCCESS;
	["--tags", Tag] -> % deprecated in favor of simply "Tag"
	    print_usage_tags(Tag, MaxC, ShCode, Version),
	    ?STATUS_SUCCESS;
	[String | _] ->
            case determine_string_type(String, Version) of
                no_idea ->
                    io:format("No tag or command matches '~ts'~n", [String]);
                both ->
                    print_usage_tags(String, MaxC, ShCode, Version),
                    print_usage_commands2(String, MaxC, ShCode, Version);
                tag ->
                    print_usage_tags(String, MaxC, ShCode, Version);
                command ->
                    print_usage_commands2(String, MaxC, ShCode, Version)
            end,
	    ?STATUS_SUCCESS
    end;

process(["--version", Arg | Args], _) ->
    Version =
	try
	    list_to_integer(Arg)
	catch _:_ ->
		throw({invalid_version, Arg})
	end,
    process(Args, Version);

process(Args, Version) ->
    {String, Code} = process2(Args, [], Version),
    case String of
	[] -> ok;
	_ ->
	    io:format("~ts~n", [String])
    end,
    Code.

%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
process2(Args, AccessCommands) ->
    process2(Args, AccessCommands, ?DEFAULT_VERSION).

%% @spec (Args::[string()], AccessCommands, Version) -> {String::string(), Code::integer()}
process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
    process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
				    list_to_binary(Pass), true}, Version);
process2(Args, AccessCommands, Version) ->
    process2(Args, AccessCommands, noauth, Version).



process2(Args, AccessCommands, Auth, Version) ->
    case try_run_ctp(Args, Auth, AccessCommands, Version) of
	{String, wrong_command_arguments}
          when is_list(String) ->
	    io:format(lists:flatten(["\n" | String]++["\n"])),
	    [CommandString | _] = Args,
            process(["help" | [CommandString]], Version),
	    {lists:flatten(String), ?STATUS_ERROR};
	{String, Code}
          when is_list(String) and is_integer(Code) ->
	    {lists:flatten(String), Code};
	String
          when is_list(String) ->
	    {lists:flatten(String), ?STATUS_SUCCESS};
	Code
          when is_integer(Code) ->
	    {"", Code};
	Other ->
	    {"Erroneous result: " ++ io_lib:format("~p", [Other]), ?STATUS_ERROR}
    end.

determine_string_type(String, Version) ->
    TagsCommands = ejabberd_commands:get_tags_commands(Version),
    CommandsNames = case lists:keysearch(String, 1, TagsCommands) of
			{value, {String, CNs}} -> CNs;
			false -> []
		    end,
    AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands(Version)],
    Cmds = filter_commands(AllCommandsNames, String),
    case {CommandsNames, Cmds} of
        {[], []} -> no_idea;
        {[], _} -> command;
        {_, []} -> tag;
        {_, _} -> both
    end.

%%-----------------------------
%% Command calling
%%-----------------------------

%% @spec (Args::[string()], Auth, AccessCommands, Version) -> string() | integer() | {string(), integer()}
try_run_ctp(Args, Auth, AccessCommands, Version) ->
    try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
	false when Args /= [] ->
	    try_call_command(Args, Auth, AccessCommands, Version);
	false ->
	    print_usage(Version),
	    {"", ?STATUS_USAGE};
	Status ->
	    {"", Status}
    catch
	exit:Why ->
	    print_usage(Version),
	    {io_lib:format("Error in ejabberd ctl process: ~p", [Why]), ?STATUS_USAGE};
	Error:Why ->
            %% In this case probably ejabberd is not started, so let's show Status
            process(["status"], Version),
            print("~n", []),
	    {io_lib:format("Error in ejabberd ctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE}
    end.

%% @spec (Args::[string()], Auth, AccessCommands, Version) -> string() | integer() | {string(), integer()}
try_call_command(Args, Auth, AccessCommands, Version) ->
    try call_command(Args, Auth, AccessCommands, Version) of
	{Reason, wrong_command_arguments} ->
	    {Reason, ?STATUS_ERROR};
	Res ->
	    Res
    catch
	throw:{error, unknown_command} ->
	    KnownCommands = [Cmd || {Cmd, _, _} <- ejabberd_commands:list_commands(Version)],
	    UnknownCommand = list_to_atom(hd(Args)),
	    {io_lib:format(
	       "Error: unknown command '~ts'. Did you mean '~ts'?",
	       [hd(Args), misc:best_match(UnknownCommand, KnownCommands)]),
	     ?STATUS_ERROR};
	throw:Error ->
	    {io_lib:format("~p", [Error]), ?STATUS_ERROR};
	?EX_RULE(A, Why, Stack) ->
	    StackTrace = ?EX_STACK(Stack),
	    {io_lib:format("Unhandled exception occurred executing the command:~n** ~ts",
			   [misc:format_exception(2, A, Why, StackTrace)]),
	     ?STATUS_ERROR}
    end.

%% @spec (Args::[string()], Auth, AccessCommands, Version) -> string() | integer() | {string(), integer()} | {error, ErrorType}
call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
    CmdStringU = ejabberd_regexp:greplace(
                   list_to_binary(CmdString), <<"-">>, <<"_">>),
    Command = list_to_atom(binary_to_list(CmdStringU)),
    {ArgsFormat, _, ResultFormat} = ejabberd_commands:get_command_format(Command, Auth, Version),
    case (catch format_args(Args, ArgsFormat)) of
	ArgsFormatted when is_list(ArgsFormatted) ->
	    CI = case Auth of
		     {U, S, _, _} -> #{usr => {U, S, <<"">>}, caller_host => S};
		     _ -> #{}
		 end,
	    CI2 = CI#{caller_module => ?MODULE},
	    Result = ejabberd_commands:execute_command2(Command,
							ArgsFormatted,
							CI2,
							Version),
	    format_result(Result, ResultFormat);
	{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
	    {NumCompa, TextCompa} =
		case {length(A1), length(A2)} of
		    {L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
		    {L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
		end,
	    process(["help" | [CmdString]]),
	    {io_lib:format("Error: the command '~ts' requires ~p ~ts.",
			   [CmdString, NumCompa, TextCompa]),
	     wrong_command_arguments}
    end.


%%-----------------------------
%% Format arguments
%%-----------------------------

format_args(Args, ArgsFormat) ->
    lists:foldl(
      fun({{_ArgName, ArgFormat}, Arg}, Res) ->
	      Formatted = format_arg(Arg, ArgFormat),
	      Res ++ [Formatted]
      end,
      [],
      lists:zip(ArgsFormat, Args)).

format_arg(Arg, integer) ->
    format_arg2(Arg, "~d");
format_arg(Arg, binary) ->
    unicode:characters_to_binary(Arg, utf8);
format_arg("", string) ->
    "";
format_arg(Arg, string) ->
    NumChars = integer_to_list(length(Arg)),
    Parse = "~" ++ NumChars ++ "c",
    format_arg2(Arg, Parse).

format_arg2(Arg, Parse)->
    {ok, [Arg2], _RemainingArguments} = io_lib:fread(Parse, Arg),
    Arg2.

%%-----------------------------
%% Format result
%%-----------------------------

format_result({error, ErrorAtom}, _) ->
    {io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)};

%% An error should always be allowed to return extended error to help with API.
%% Extended error is of the form:
%%  {error, type :: atom(), code :: int(), Desc :: string()}
format_result({error, ErrorAtom, Code, Msg}, _) ->
    {io_lib:format("Error: ~p: ~s", [ErrorAtom, Msg]), make_status(Code)};

format_result(Atom, {_Name, atom}) ->
    io_lib:format("~p", [Atom]);

format_result(Int, {_Name, integer}) ->
    io_lib:format("~p", [Int]);

format_result([A|_]=String, {_Name, string}) when is_list(String) and is_integer(A) ->
    io_lib:format("~ts", [String]);

format_result(Binary, {_Name, string}) when is_binary(Binary) ->
    io_lib:format("~ts", [binary_to_list(Binary)]);

format_result(Atom, {_Name, string}) when is_atom(Atom) ->
    io_lib:format("~ts", [atom_to_list(Atom)]);

format_result(Integer, {_Name, string}) when is_integer(Integer) ->
    io_lib:format("~ts", [integer_to_list(Integer)]);

format_result(Other, {_Name, string})  ->
    io_lib:format("~p", [Other]);

format_result(Code, {_Name, rescode}) ->
    make_status(Code);

format_result({Code, Text}, {_Name, restuple}) ->
    {io_lib:format("~ts", [Text]), make_status(Code)};

%% The result is a list of something: [something()]
format_result([], {_Name, {list, _ElementsDef}}) ->
    "";
format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) ->
    %% Start formatting the first element
    [format_result(FirstElement, ElementsDef) |
     %% If there are more elements, put always first a newline character
     lists:map(
       fun(Element) ->
	       ["\n" | format_result(Element, ElementsDef)]
       end,
       Elements)];

%% The result is a tuple with several elements: {something1(), something2(),...}
%% NOTE: the elements in the tuple are separated with tabular characters,
%% if a string is empty, it will be difficult to notice in the shell,
%% maybe a different separation character should be used, like ;;?
format_result(ElementsTuple, {_Name, {tuple, ElementsDef}}) ->
    ElementsList = tuple_to_list(ElementsTuple),
    [{FirstE, FirstD} | ElementsAndDef] = lists:zip(ElementsList, ElementsDef),
    [format_result(FirstE, FirstD) |
     lists:map(
       fun({Element, ElementDef}) ->
	       ["\t" | format_result(Element, ElementDef)]
       end,
       ElementsAndDef)];

format_result(404, {_Name, _}) ->
    make_status(not_found).

make_status(ok) -> ?STATUS_SUCCESS;
make_status(true) -> ?STATUS_SUCCESS;
make_status(Code) when is_integer(Code), Code > 255 -> ?STATUS_ERROR;
make_status(Code) when is_integer(Code), Code > 0 -> Code;
make_status(Error) ->
    io:format("Error: ~p~n", [Error]),
    ?STATUS_ERROR.

get_list_commands(Version) ->
    try ejabberd_commands:list_commands(Version) of
	Commands ->
	    [tuple_command_help(Command)
	     || {N,_,_}=Command <- Commands,
		%% Don't show again those commands, because they are already
		%% announced by ejabberd_ctl itself
		N /= status, N /= stop, N /= restart]
    catch
	exit:_ ->
	    []
    end.

%% Return: {string(), [string()], string()}
tuple_command_help({Name, _Args, Desc}) ->
    {Args, _, _} = ejabberd_commands:get_command_format(Name, admin),
    Arguments = [atom_to_list(ArgN) || {ArgN, _ArgF} <- Args],
    Prepend = case is_supported_args(Args) of
		  true -> "";
		  false -> "*"
	      end,
    CallString = atom_to_list(Name),
    {CallString, Arguments, Prepend ++ Desc}.

is_supported_args(Args) ->
    lists:all(
      fun({_Name, Format}) ->
	      (Format == integer)
		  or (Format == string)
		      or (Format == binary)
      end,
      Args).

%%-----------------------------
%% Print help
%%-----------------------------

%% Commands are Bold
-define(B1, "\e[1m").
-define(B2, "\e[21m").
-define(C(S), case ShCode of true -> [?B1, S, ?B2]; false -> S end).

%% Arguments are Dim
-define(D1, "\e[2m").
-define(D2, "\e[22m").
-define(A(S), case ShCode of true -> [?D1, S, ?D2]; false -> S end).

%% Tags are Underline
-define(U1, "\e[4m").
-define(U2, "\e[24m").
-define(T(S), case ShCode of true -> [?U1, S, ?U2]; false -> S end).

%% B are Nothing
-define(N1, "\e[0m").
-define(N2, "\e[0m").
-define(B(S), case ShCode of true -> [?N1, S, ?N2]; false -> S end).

print_usage(Version) ->
    {MaxC, ShCode} = get_shell_info(),
    print_usage(dual, MaxC, ShCode, Version).
print_usage(HelpMode, MaxC, ShCode, Version) ->
    AllCommands =
	[
	 {"status", [], "Get ejabberd status"},
	 {"stop", [], "Stop ejabberd"},
	 {"restart", [], "Restart ejabberd"},
	 {"mnesia", ["[info]"], "show information of Mnesia system"}] ++
	get_list_commands(Version),

    print(
       ["Usage: ", "ejabberdctl", " [--no-timeout] [--node ", ?A("nodename"), "] [--version ", ?A("api_version"), "] ",
	?C("command"), " [", ?A("arguments"), "]\n"
	"\n"
	"Available commands in this ejabberd node:\n"], []),
    print_usage_commands(HelpMode, MaxC, ShCode, AllCommands).

print_usage_commands(HelpMode, MaxC, ShCode, Commands) ->
    CmdDescsSorted = lists:keysort(1, Commands),

    %% What is the length of the largest command?
    {CmdArgsLenDescsSorted, Lens} =
	lists:mapfoldl(
	  fun({Cmd, Args, Desc}, Lengths) ->
		  Len =
		      length(Cmd) +
		      lists:foldl(fun(Arg, R) ->
					  R + 1 + length(Arg)
				  end,
				  0,
				  Args),
		  {{Cmd, Args, Len, Desc}, [Len | Lengths]}
	  end,
	  [],
	  CmdDescsSorted),
    MaxCmdLen = case Lens of
		    [] -> 80;
		    _ -> lists:max(Lens)
		end,

    %% For each command in the list of commands
    %% Convert its definition to a line
    FmtCmdDescs = format_command_lines(CmdArgsLenDescsSorted, MaxCmdLen, MaxC, ShCode, HelpMode),

    print([FmtCmdDescs], []).


%% Get some info about the shell:
%% how many columns of width
%% and guess if it supports text formatting codes.
get_shell_info() ->
    %% This function was introduced in OTP R12B-0
    try io:columns() of
	{ok, C} -> {C-2, true};
	{error, enotsup} -> {78, false}
    catch
	_:_ -> {78, false}
    end.

%% Erlang/OTP 20.0 introduced string:find/2, but we must support old 19.3
string_find([], _SearchPattern) ->
    nomatch;
string_find([A | String], [A]) ->
    String;
string_find([_ | String], SearchPattern) ->
    string_find(String, SearchPattern).

%% Split this command description in several lines of proper length
prepare_description(DescInit, MaxC, Desc) ->
    case string_find(Desc, "\n") of
        nomatch ->
            prepare_description2(DescInit, MaxC, Desc);
        _ ->
            Desc
    end.

prepare_description2(DescInit, MaxC, Desc) ->
    Words = string:tokens(Desc, " "),
    prepare_long_line(DescInit, MaxC, Words).

prepare_long_line(DescInit, MaxC, Words) ->
    MaxSegmentLen = MaxC - DescInit,
    MarginString = lists:duplicate(DescInit, $\s), % Put spaces
    [FirstSegment | MoreSegments] = split_desc_segments(MaxSegmentLen, Words),
    MoreSegmentsMixed = mix_desc_segments(MarginString, MoreSegments),
    [FirstSegment | MoreSegmentsMixed].

mix_desc_segments(MarginString, Segments) ->
    [["\n", MarginString, Segment] || Segment <- Segments].

split_desc_segments(MaxL, Words) ->
    join(MaxL, Words).

%% Join words in a segment,
%% but stop adding to a segment if adding this word would pass L
join(L, Words) ->
    join(L, Words, 0, [], []).

join(_Len, [], _CurSegLen, CurSeg, AllSegs) ->
    lists:reverse([CurSeg | AllSegs]);
join(Len, [Word | Tail], CurSegLen, CurSeg, AllSegs) ->
    WordLen = length(Word),
    SegSize = WordLen + CurSegLen + 1,
    {NewCurSeg, NewAllSegs, NewCurSegLen} =
        if SegSize < Len ->
                {[CurSeg, " ", Word], AllSegs, SegSize};
           true ->
                {Word, [CurSeg | AllSegs], WordLen}
        end,
    NewLen = case string:str(Word, "\n") of
                 0 ->
                     NewCurSegLen;
                 _ ->
                     0
             end,
    join(Len, Tail, NewLen, NewCurSeg, NewAllSegs).


format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual)
  when MaxC - MaxCmdLen < 40 ->
    %% If the space available for descriptions is too narrow, enforce long help mode
    format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, long);

format_command_lines(CALD, _MaxCmdLen, _MaxC, ShCode, short) ->
    lists:map(
      fun({Cmd, Args, _CmdArgsL, _Desc}) ->
	      ["    ", ?C(Cmd), [[" ", ?A(Arg)] || Arg <- Args], "\n"]
      end, CALD);

format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual) ->
    lists:map(
      fun({Cmd, Args, CmdArgsL, Desc}) ->
	      DescFmt = prepare_description(MaxCmdLen+4, MaxC, Desc),
	      ["  ", ?C(Cmd), [[" ", ?A(Arg)] || Arg <- Args],
               lists:duplicate(MaxCmdLen - CmdArgsL + 1, $\s),
	       DescFmt, "\n"]
      end, CALD);

format_command_lines(CALD, _MaxCmdLen, MaxC, ShCode, long) ->
    lists:map(
      fun({Cmd, Args, _CmdArgsL, Desc}) ->
	      DescFmt = prepare_description(13, MaxC, Desc),
	      ["  ", ?C(Cmd), [[" ", ?A(Arg)] || Arg <- Args], "\n",
               "            ", DescFmt, "\n"]
      end, CALD).


%%-----------------------------
%% Print Tags
%%-----------------------------

print_usage_tags(MaxC, ShCode, Version) ->
    print("Available tags and list of commands:", []),
    TagsCommands = ejabberd_commands:get_tags_commands(Version),
    lists:foreach(
      fun({Tag, Commands} = _TagCommands) ->
	      print(["\n\n  ", ?T(Tag), "\n    "], []),
	      Words = lists:sort(Commands),
	      Desc = prepare_long_line(5, MaxC, Words),
	      print(?C(Desc), [])
      end,
      TagsCommands),
    print("\n\n", []).

print_usage_tags_long(MaxC, ShCode, Version) ->
    print("Available tags and commands details:", []),
    TagsCommands = ejabberd_commands:get_tags_commands(Version),
    print("\n", []),
    lists:foreach(
      fun({Tag, CommandsNames} = _TagCommands) ->
	      print(["\n  ", ?T(Tag), "\n"], []),
                CommandsList = lists:map(
                                 fun(NameString) ->
                                         C = ejabberd_commands:get_command_definition(
                                               list_to_atom(NameString), Version),
                                         #ejabberd_commands{name = Name,
                                                            args = Args,
                                                            desc = Desc} = C,
                                         tuple_command_help({Name, Args, Desc})
                                 end,
                                 CommandsNames),
                print_usage_commands(short, MaxC, ShCode, CommandsList)
      end,
      TagsCommands),
    print("\n", []).

print_usage_tags(Tag, MaxC, ShCode, Version) ->
    print(["Available commands with tag ", ?T(Tag), ":", "\n", "\n"], []),
    HelpMode = long,
    TagsCommands = ejabberd_commands:get_tags_commands(Version),
    CommandsNames = case lists:keysearch(Tag, 1, TagsCommands) of
			{value, {Tag, CNs}} -> CNs;
			false -> []
		    end,
    CommandsList = lists:map(
		     fun(NameString) ->
			     C = ejabberd_commands:get_command_definition(
                                   list_to_atom(NameString), Version),
			     #ejabberd_commands{name = Name,
						args = Args,
						desc = Desc} = C,
			     tuple_command_help({Name, Args, Desc})
		     end,
		     CommandsNames),
    print_usage_commands(HelpMode, MaxC, ShCode, CommandsList),
    print("\n", []).


%%-----------------------------
%% Print usage of 'help' command
%%-----------------------------

print_usage_help(MaxC, ShCode) ->
    LongDesc =
	["This special ", ?C("help"), " command provides help of ejabberd commands.\n\n"
	 "The format is:\n  ", ?B("ejabberdctl"), " ", ?C("help"),
         " [", ?A("tags"), " | ", ?A("commands"), " | ", ?T("tag"), " | ", ?C("command"), " | ", ?C("com?*"), "]\n\n"
	 "The optional arguments:\n"
	 "  ",?A("tags"),"         Show all tags and commands names in each tag\n"
	 "  ",?A("commands"),"     Show all tags and commands details in each tag\n"
	 "  ",?T("tag"),"          Show commands related to this tag\n"
	 "  ",?C("command"),"      Show detailed description of this command\n"
	 "  ",?C("com?*"),"        Show commands that match this glob.\n"
	 "               (? will match a simple character, and\n"
	 "                * will match several characters)\n"
	 "\n",
	 "Some example usages:\n",
	 "  ejabberdctl ", ?C("help"), "\n",
	 "  ejabberdctl ", ?C("help"), " ", ?A("tags"), "\n",
	 "  ejabberdctl ", ?C("help"), " ", ?A("commands"), "\n",
	 "  ejabberdctl ", ?C("help"), " ", ?T("accounts"), "\n",
	 "  ejabberdctl ", ?C("help"), " ", ?C("register"), "\n",
	 "  ejabberdctl ", ?C("help"), " ", ?C("regist*"), "\n",
	 "\n",
	 "Please note that 'ejabberdctl' shows all ejabberd commands,\n",
	 "even those that cannot be used in the shell with ejabberdctl.\n",
	 "Those commands can be identified because their description starts with: *"],
    ArgsDef = [],
    C = #ejabberd_commands{
	   name = help,
	   desc = "Show help of ejabberd commands",
	   longdesc = lists:flatten(LongDesc),
	   args = ArgsDef,
	   result = {help, string}},
    print_usage_command2("help", C, MaxC, ShCode).


%%-----------------------------
%% Print usage command
%%-----------------------------

%% @spec (CmdSubString::string(), MaxC::integer(), ShCode::boolean(), Version) -> ok
print_usage_commands2(CmdSubString, MaxC, ShCode, Version) ->
    %% Get which command names match this substring
    AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands(Version)],
    Cmds = filter_commands(AllCommandsNames, CmdSubString),
    case Cmds of
	[] -> io:format("Error: no command found that match '~ts'~n", [CmdSubString]);
	_ -> print_usage_commands3(lists:sort(Cmds), MaxC, ShCode, Version)
    end.

print_usage_commands3([Cmd], MaxC, ShCode, Version) ->
    print_usage_command(Cmd, MaxC, ShCode, Version);
print_usage_commands3(Cmds, MaxC, ShCode, Version) ->
        CommandsList = lists:map(
		     fun(NameString) ->
			     C = ejabberd_commands:get_command_definition(
                                   list_to_atom(NameString), Version),
			     #ejabberd_commands{name = Name,
						args = Args,
						desc = Desc} = C,
			     tuple_command_help({Name, Args, Desc})
		     end,
		     Cmds),

	      print_usage_commands(long, MaxC, ShCode, CommandsList), %% que aqui solo muestre un par de lineas
              ok.

filter_commands(All, SubString) ->
    case lists:member(SubString, All) of
	true -> [SubString];
	false -> filter_commands_regexp(All, SubString)
    end.

filter_commands_regexp(All, Glob) ->
    RegExp = ejabberd_regexp:sh_to_awk(list_to_binary(Glob)),
    lists:filter(
      fun(Command) ->
	      case ejabberd_regexp:run(list_to_binary(Command), RegExp) of
		  match ->
		      true;
		  nomatch ->
		      false
	      end
      end,
      All).

%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean(), Version) -> ok
print_usage_command(Cmd, MaxC, ShCode, Version) ->
    Name = list_to_atom(Cmd),
    C = ejabberd_commands:get_command_definition(Name, Version),
    print_usage_command2(Cmd, C, MaxC, ShCode).

print_usage_command2(Cmd, C, MaxC, ShCode) ->
    #ejabberd_commands{
		     tags = TagsAtoms,
		     desc = Desc,
		     args = ArgsDef,
		     longdesc = LongDesc,
		     result = ResultDef} = C,

    NameFmt = ["  ", ?B("Command Name"), ": ", ?C(Cmd), "\n"],

    %% Initial indentation of result is 13 = length("  Arguments: ")
    Args = [format_usage_ctype(ArgDef, 13) || ArgDef <- ArgsDef],
    ArgsMargin = lists:duplicate(13, $\s),
    ArgsListFmt = case Args of
		      [] -> "\n";
		      _ -> [ [Arg, "\n", ArgsMargin] || Arg <- Args]
		  end,
    ArgsFmt = ["  ", ?B("Arguments"), ": ", ArgsListFmt],

    %% Initial indentation of result is 11 = length("  Returns: ")
    ResultFmt = format_usage_ctype(ResultDef, 11),
    ReturnsFmt = ["  ",?B("Returns"),": ", ResultFmt],

    XmlrpcFmt = "", %%+++ ["  ",?B("XML-RPC"),": ", format_usage_xmlrpc(ArgsDef, ResultDef), "\n\n"],

    TagsFmt = ["  ",?B("Tags"),":", prepare_long_line(8, MaxC, [?T(atom_to_list(TagA)) || TagA <- TagsAtoms])],

    DescFmt = ["  ",?B("Description"),":", prepare_description(15, MaxC, Desc)],

    LongDescFmt = case LongDesc of
		      "" -> "";
		      _ -> ["", prepare_description(0, MaxC, LongDesc), "\n\n"]
		  end,

    NoteEjabberdctl = case is_supported_args(ArgsDef) of
			  true -> "";
			  false -> ["  ", ?B("Note:"), " This command cannot be executed using ejabberdctl. Try ejabberd_xmlrpc.\n\n"]
		      end,

    case Cmd of
        "help" -> ok;
        _ -> print([NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt,
                    "\n\n", XmlrpcFmt, TagsFmt, "\n\n", DescFmt, "\n\n"], [])
    end,
    print([LongDescFmt, NoteEjabberdctl], []).

format_usage_ctype(Type, _Indentation)
  when (Type==atom) or (Type==integer) or (Type==string) or (Type==binary) or (Type==rescode) or (Type==restuple)->
    io_lib:format("~p", [Type]);

format_usage_ctype({Name, Type}, _Indentation)
  when (Type==atom) or (Type==integer) or (Type==string) or (Type==binary) or (Type==rescode) or (Type==restuple)->
    io_lib:format("~p::~p", [Name, Type]);

format_usage_ctype({Name, {list, ElementDef}}, Indentation) ->
    NameFmt = atom_to_list(Name),
    Indentation2 = Indentation + length(NameFmt) + 4,
    ElementFmt = format_usage_ctype(ElementDef, Indentation2),
    [NameFmt, "::[ ", ElementFmt, " ]"];

format_usage_ctype({Name, {tuple, ElementsDef}}, Indentation) ->
    NameFmt = atom_to_list(Name),
    Indentation2 = Indentation + length(NameFmt) + 4,
    ElementsFmt = format_usage_tuple(ElementsDef, Indentation2),
    [NameFmt, "::{ " | ElementsFmt].

format_usage_tuple([], _Indentation) ->
    [];
format_usage_tuple([ElementDef], Indentation) ->
    [format_usage_ctype(ElementDef, Indentation) , " }"];
format_usage_tuple([ElementDef | ElementsDef], Indentation) ->
    ElementFmt = format_usage_ctype(ElementDef, Indentation),
    MarginString = lists:duplicate(Indentation, $\s), % Put spaces
    [ElementFmt, ",\n", MarginString, format_usage_tuple(ElementsDef, Indentation)].

print(Format, Args) ->
    io:format(lists:flatten(Format), Args).

-ifdef(LAGER).
disable_logging() ->
    ok.
-else.
disable_logging() ->
    logger:set_primary_config(level, none).
-endif.