%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2011. 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%
%%
%% Dynamic Driver Loader and Linker
%%
%% Interface for dynamic library/shared object driver loader/linker.
%% Provides methods for loading, unloading and listing drivers.

-module(erl_ddll).

-export([load_driver/2, load/2, 
	 unload_driver/1, unload/1, reload/2, reload_driver/2, 
	 format_error/1,info/1,info/0, start/0, stop/0]).

%%----------------------------------------------------------------------------

-type path()   :: string() | atom().
-type driver() :: string() | atom().

%%----------------------------------------------------------------------------

-spec start() -> {'error', {'already_started', 'undefined'}}.

start() ->
    {error, {already_started,undefined}}.

-spec stop() -> 'ok'.

stop() ->
    ok.

-spec load_driver(Path, Name) -> 'ok' | {'error', ErrorDesc} when
      Path :: path(),
      Name :: driver(),
      ErrorDesc :: term().

load_driver(Path, Driver) ->
    do_load_driver(Path, Driver, [{driver_options,[kill_ports]}]).

-spec load(Path, Name) -> 'ok' | {'error', ErrorDesc} when
      Path :: path(),
      Name :: driver(),
      ErrorDesc ::term().

load(Path, Driver) ->
    do_load_driver(Path, Driver, []).

do_load_driver(Path, Driver, DriverFlags) ->
    case erl_ddll:try_load(Path, Driver,[{monitor,pending_driver}]++DriverFlags) of
	{error, inconsistent} ->
	    {error,bad_driver_name}; % BC 
	{error, What} ->
	    {error,What};
	{ok, already_loaded} ->
	    ok;
	{ok,loaded} ->
	    ok;
	{ok, pending_driver, Ref} ->
	    receive
		{'DOWN', Ref, driver, _, load_cancelled} ->
		    {error, load_cancelled};
		{'UP', Ref, driver, _, permanent} ->
		    {error, permanent}; 
		{'DOWN', Ref, driver, _, {load_failure, Failure}} ->
		    {error, Failure};
		{'UP', Ref, driver, _, loaded} ->
		    ok
	    end
    end.

do_unload_driver(Driver,Flags) ->
    case erl_ddll:try_unload(Driver,Flags) of
	{error,What} ->
	    {error,What};
	{ok, pending_process} ->
	    ok;
	{ok, unloaded} ->
	    ok;
	{ok, pending_driver} ->
	    ok;
	{ok, pending_driver, Ref} ->
	    receive
		{'UP', Ref, driver, _, permanent} ->
		    {error, permanent}; 
		{'UP', Ref, driver, _, unload_cancelled} ->
		    ok;
		{'DOWN', Ref, driver, _, unloaded} ->
		    ok
	    end
    end.

-spec unload_driver(Name) -> 'ok' | {'error', ErrorDesc} when
      Name :: driver(),
      ErrorDesc :: term().

unload_driver(Driver) ->
    do_unload_driver(Driver,[{monitor,pending_driver},kill_ports]).

-spec unload(Name) -> 'ok' | {'error', ErrorDesc} when
      Name :: driver(),
      ErrorDesc :: term().

unload(Driver) ->
    do_unload_driver(Driver,[]).

-spec reload(Path, Name) -> 'ok' | {'error', ErrorDesc} when
      Path :: path(),
      Name :: driver(),
      ErrorDesc :: pending_process | OpaqueError,
      OpaqueError :: term().

reload(Path,Driver) ->
    do_load_driver(Path, Driver, [{reload,pending_driver}]).

-spec reload_driver(Path, Name) -> 'ok' | {'error', ErrorDesc} when
      Path :: path(),
      Name :: driver(),
      ErrorDesc :: pending_process | OpaqueError,
      OpaqueError :: term().

reload_driver(Path,Driver) ->
    do_load_driver(Path, Driver, [{reload,pending_driver},
				  {driver_options,[kill_ports]}]).			    

-spec format_error(ErrorDesc) -> string() when
      ErrorDesc :: term().

format_error(Code) ->
    case Code of
	%% This is the only error code returned only from erlang code...
	%% 'permanent' has a translation in the emulator, even though the erlang code uses it to...
	load_cancelled ->
	    "Loading was cancelled from other process";
	_ ->
	    erl_ddll:format_error_int(Code)
    end.

-spec info(Name) -> InfoList when
      Name :: driver(),
      InfoList :: [InfoItem, ...],
      InfoItem :: {Tag :: atom(), Value :: term()}.
 
info(Driver) ->
    [{processes, erl_ddll:info(Driver,processes)},
     {driver_options, erl_ddll:info(Driver,driver_options)},
     {port_count, erl_ddll:info(Driver,port_count)},
     {linked_in_driver, erl_ddll:info(Driver,linked_in_driver)},
     {permanent, erl_ddll:info(Driver,permanent)},
     {awaiting_load,  erl_ddll:info(Driver,awaiting_load)},
     {awaiting_unload, erl_ddll:info(Driver,awaiting_unload)}].

-spec info() -> AllInfoList when
      AllInfoList :: [DriverInfo],
      DriverInfo :: {DriverName, InfoList},
      DriverName :: string(),
      InfoList :: [InfoItem],
      InfoItem :: {Tag :: atom(), Value :: term()}.

info() ->
    {ok,DriverList} = erl_ddll:loaded_drivers(),
    [{X,Y} || X <- DriverList,
	       Y <- [catch info(X)],
	       is_list(Y), not lists:member({linked_in_driver,true},Y)].