%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%
%%
%%----------------------------------------------------------------------
%% Purpose : A front-end to the erlang:process_info() functions, that
%%           can handle processes on different nodes in a transparent
%%           way. 
%%	     Also some convenience functions for process info, as well
%%	     as some application specific functions for process 
%%           classification.
%%----------------------------------------------------------------------

-module(pman_process).

-export([pinfo/1, pinfo/2,
	 r_processes/1,
	 function_info/1,
	 get_name/1, msg/1, reds/1, psize/1,
	 is_running/1,
	 is_pid_or_shell/1,
	 get_pid/1,
	 is_system_process/1,
	 is_hidden_by_module/2
	]).

%% List of registered name that will make a prodcess a "SYSTEM"-process 
-define(SYSTEM_REG_NAMES,
	[
	 %% kernel
	 application_controller,
	 erl_reply,
	 auth,
	 boot_server,
	 code_server,
	 disk_log_server,
	 disk_log_sup,
	 erl_prim_loader,
	 error_logger,
	 file_server_2,
	 fixtable_server,
	 global_group,
	 global_name_server,
	 heart,
	 inet_gethost_native,
	 inet_gethost_native_sup,
	 init,
	 kernel_config,
	 kernel_safe_sup,
	 kernel_sup,
	 net_kernel,
	 net_sup,
	 rex,
	 user,
	 os_server,
	 ddll_server,
	 erl_epmd,
	 inet_db,
	 pg2,

	 %% stdlib
	 timer_server,
	 rsh_starter,
	 take_over_monitor,
	 pool_master,
	 dets,

	 %% sasl
	 sasl_safe_sup, sasl_sup, alarm_handler, overload,
	 release_handler,

	 %% gs
	 gs_frontend
	]).
	 
%% List of module:function/arity calls that will make the caller a 
%% "SYSTEM"-process.
%% 
-define(SYSTEM_INIT_CALLS,
	[{application_master,init,4},
	 {application_master,start_it,4},
	 {inet_tcp_dist,accept_loop,2},
	 {net_kernel,ticker,2},
	 {supervisor_bridge,user_sup,1},
	 {user_drv,server,2},
	 {group,server,3},
	 {kernel_config,init,1},
	 {inet_tcp_dist,do_accept,6},
	 {inet_tcp_dist,do_setup,6},
	 {pman_main,init,2},
	 {pman_buf_printer,init,2},
	 {pman_buf_converter,init,2},
	 {pman_buf_buffer,init,1},
	 {gstk,init,1},
	 {gstk_port_handler,init,2},
	 {gstk,worker_init,1}
	]).

%% List of module:function/arity calls that will make the executing
%% process a "SYSTEM"-process.
-define(SYSTEM_RUNNING_CALLS,
	[{file_io_server,server_loop,1},
	 {global,loop_the_locker,1},
	 {global,collect_deletions,2},
	 {global,loop_the_registrar,0},
	 {gs_frontend,request,2},
	 {shell,get_command1,5},
	 {shell,eval_loop,3},
	 {io,wait_io_mon_reply,2},
	 {pman_module_info,loop,1},
	 {pman_options,dialog,3},
	 {pman_options,loop,1},
	 {pman_relay_server,loop,1},
	 {pman_shell,monitor_loop,1},
	 {pman_shell,safe_loop,2}
	]).

%% pinfo(Pid) -> [{Item, Info}] | undefined
%% pinfo(Pid, Item) -> Info | undefined
%% A version of process_info/1 that handles pid on remote nodes as well.
pinfo({_, Pid}) -> % Handle internal process format
    pinfo(Pid);
pinfo(Pid) when node(Pid)==node() ->
    process_info(Pid);
pinfo(Pid) ->
    case rpc:call(node(Pid), erlang, process_info, [Pid]) of
	{badrpc, _} -> undefined;
	Res -> Res
    end.

pinfo({_, Pid}, Item) -> % Handle internal process format
    pinfo(Pid, Item);
pinfo(Pid, Item) when node(Pid)==node() ->
    case process_info(Pid, Item) of
	{Item, Info} -> Info;
	"" -> ""; % Item == registered_name
	undefined -> undefined
    end;
pinfo(Pid, Item) ->
    case rpc:call(node(Pid), erlang, process_info, [Pid, Item]) of
	{badrpc, _} -> undefined;
	{Item, Info} -> Info;
	"" -> ""; % Item == registered_name
	undefined -> undefined
    end.

%% function_info(Pid) -> {M, F, A}
%% Returns the initial function for the specified process. 
function_info(Pid) ->
    case pinfo(Pid, current_function) of
	{Module, Function, Arity} ->
	    {Module, Function, Arity};
	undefined ->
	    {unknown, unknown, 0}
    end.

%% r_processes(Node) -> Pids
%% Return a list of all processes at Node.
%%
%% If there is a problem with getting information from a remote 
%% node, an empty list is returned.
r_processes(Node) ->
    ordsets:from_list(r_processes1(Node)).

r_processes1(Node) ->
    if
	Node==node() ->
	    processes();
	true ->
	    case rpc:block_call(Node, erlang, processes, []) of
		{badrpc, _} ->
		    [];
		Pids -> Pids
	    end
    end.

%% is_running(Object) -> {true, {shell,Pid}} | {true, Pid} | false
%%   Object = {shell, Pid} | {link, Pid, ?} | Pid
is_running({shell,Pid}) -> 
    case is_running(Pid) of
	{true,Pid} ->
	    {true,{shell,Pid}};
	false ->
	    false
    end;
is_running({link,Pid,_}) -> 
    is_running(Pid);
is_running(Pid) ->
    case is_pid_or_shell(Pid) of
	true ->
	    case pinfo(Pid) of
		undefined -> false;
		_PInfo -> {true, Pid}
	    end;
	false ->
	    false
    end.

%% is_pid_or_shell(Object) -> bool()
%% Checks if the argument is an pid or a tuple {shell, Pid}.
is_pid_or_shell({shell,Pid}) when is_pid(Pid) ->
    true;
is_pid_or_shell(Pid) when is_pid(Pid) ->
    true;
is_pid_or_shell(_) ->
    false.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% get_pid/1 - returns the Pid of the object provided that 
%%   it is a proper process specifier.
%%
%% Arguments:
%%   Object	A process specifier
%%
%% Returns:
%%   The Pid.

get_pid({shell,Pid}) ->
    Pid;
get_pid(Pid) when is_pid(Pid) ->
    Pid.

%% is_system_process(Pid) -> bool()
%% Returns true if Pid is a "system process".
%% This is a prototype version, use file configuration later.
is_system_process(Pid) ->
    catch is_system_process2(Pid).

is_system_process2(Pid) ->
    
    %% Test if the registered name is a system registered name
    case pinfo(Pid, registered_name) of
	undefined -> ignore;
	"" -> ignore;
	Name ->
	    case lists:member(Name, ?SYSTEM_REG_NAMES) of
		true -> throw(true);
		false -> ignore
	    end
    end,
    
    %% Test if the start specification is a "system start function"
    MFAi = case pinfo(Pid, initial_call) of
	       {proc_lib, init_p, 5} ->
		   proc_lib:translate_initial_call(Pid); % {M,F,A} | Fun
	       Res -> Res % {M,F,A} | undefined
	   end,
    case lists:member(MFAi, ?SYSTEM_INIT_CALLS) of
	true -> throw(true);
	false -> ignore
    end,

    %% Test if the running specification is a "system running function"
    case pinfo(Pid, current_function) of
	undefined -> false;
	MFAc ->
	    lists:member(MFAc, ?SYSTEM_RUNNING_CALLS)
    end.

%% is_hidden_by_module(Pid, Modules) -> bool()
%% Checks if Pid is to be hidden because it executes code from one
%% of Modules
is_hidden_by_module(Pid, Modules) ->
    case pinfo(Pid, current_function) of
	{Module, _Function, _Arity} ->
	    lists:member(Module, Modules);
	undefined -> false
    end.

%% get_name(Pid) -> Name | " "
%% Returns the registered name of a process, if any, or " " otherwise.
get_name(Pid) ->
    case pinfo(Pid, registered_name) of
	undefined -> " ";
	"" -> " ";
	Name -> Name
    end.

%% msg(Pid) -> int()
msg(Pid) ->
    case pinfo(Pid, messages) of
	undefined -> 0;
	Msgs -> length(Msgs)
    end.

%% reds(Pid) -> int()
reds(Pid) ->
    case pinfo(Pid, reductions) of
	undefined -> 0;	    
	Reds -> Reds
    end.

%% psize(Pid) -> int()
%% Returns the total process size (stack + heap).
psize(Pid) ->
    Stack = pinfo(Pid, stack_size),
    Heap = pinfo(Pid, heap_size),
    case {Heap, Stack} of
	{undefined, undefined} -> 0;
	{undefined, Sz} -> Sz;
	{Sz, undefined} -> Sz;
	{Sz0, Sz1}  -> Sz0 + Sz1
    end.