aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/src/code.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/src/code.erl')
-rw-r--r--lib/kernel/src/code.erl491
1 files changed, 491 insertions, 0 deletions
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
new file mode 100644
index 0000000000..fef11d7e6e
--- /dev/null
+++ b/lib/kernel/src/code.erl
@@ -0,0 +1,491 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-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%
+%%
+-module(code).
+
+%% This is the interface module to the code server. It also contains
+%% some implementation details. See also related modules: code_*.erl
+%% in this directory.
+
+-export([objfile_extension/0,
+ set_path/1,
+ get_path/0,
+ load_file/1,
+ ensure_loaded/1,
+ load_abs/1,
+ load_abs/2,
+ load_binary/3,
+ load_native_partial/2,
+ load_native_sticky/3,
+ delete/1,
+ purge/1,
+ soft_purge/1,
+ is_loaded/1,
+ all_loaded/0,
+ stop/0,
+ root_dir/0,
+ lib_dir/0,
+ lib_dir/1,
+ lib_dir/2,
+ compiler_dir/0,
+ priv_dir/1,
+ stick_dir/1,
+ unstick_dir/1,
+ stick_mod/1,
+ unstick_mod/1,
+ is_sticky/1,
+ get_object_code/1,
+ add_path/1,
+ add_pathsz/1,
+ add_paths/1,
+ add_pathsa/1,
+ add_patha/1,
+ add_pathz/1,
+ del_path/1,
+ replace_path/2,
+ rehash/0,
+ start_link/0, start_link/1,
+ which/1,
+ where_is_file/1,
+ where_is_file/2,
+ set_primary_archive/2,
+ clash/0]).
+
+-include_lib("kernel/include/file.hrl").
+
+%% User interface.
+%%
+%% objfile_extension() -> ".beam"
+%% set_path(Dir*) -> true
+%% get_path() -> Dir*
+%% add_path(Dir) -> true | {error, What}
+%% add_patha(Dir) -> true | {error, What}
+%% add_pathz(Dir) -> true | {error, What}
+%% add_paths(DirList) -> true | {error, What}
+%% add_pathsa(DirList) -> true | {error, What}
+%% add_pathsz(DirList) -> true | {error, What}
+%% del_path(Dir) -> true | {error, What}
+%% replace_path(Name,Dir) -> true | {error, What}
+%% load_file(File) -> {error,What} | {module, Mod}
+%% load_abs(File) -> {error,What} | {module, Mod}
+%% load_abs(File,Mod) -> {error,What} | {module, Mod}
+%% load_binary(Mod,File,Bin) -> {error,What} | {module,Mod}
+%% ensure_loaded(Module) -> {error,What} | {module, Mod}
+%% delete(Module)
+%% purge(Module) kills all procs running old code
+%% soft_purge(Module) -> true | false
+%% is_loaded(Module) -> {file, File} | false
+%% all_loaded() -> {Module, File}*
+%% get_object_code(Mod) -> error | {Mod, Bin, Filename}
+%% stop() -> true
+%% root_dir()
+%% compiler_dir()
+%% lib_dir()
+%% priv_dir(Name)
+%% stick_dir(Dir) -> ok | error
+%% unstick_dir(Dir) -> ok | error
+%% is_sticky(Module) -> true | false
+%% which(Module) -> Filename
+%% set_primary_archive((FileName, Bin) -> ok | {error, Reason}
+%% clash() -> -> print out
+
+%%----------------------------------------------------------------------------
+%% Some types for basic exported functions of this module
+%%----------------------------------------------------------------------------
+
+-type load_error_rsn() :: 'badfile' | 'native_code' | 'nofile' | 'not_purged'
+ | 'sticky_directory'. % for some functions only
+-type load_ret() :: {'error', load_error_rsn()} | {'module', atom()}.
+-type loaded_ret_atoms() :: 'cover_compiled' | 'preloaded'.
+-type loaded_filename() :: file:filename() | loaded_ret_atoms().
+
+%%----------------------------------------------------------------------------
+%% User interface
+%%----------------------------------------------------------------------------
+
+-spec objfile_extension() -> file:filename().
+objfile_extension() ->
+ init:objfile_extension().
+
+-spec load_file(Module :: atom()) -> load_ret().
+load_file(Mod) when is_atom(Mod) ->
+ call({load_file,Mod}).
+
+-spec ensure_loaded(Module :: atom()) -> load_ret().
+ensure_loaded(Mod) when is_atom(Mod) ->
+ call({ensure_loaded,Mod}).
+
+%% XXX File as an atom is allowed only for backwards compatibility.
+-spec load_abs(Filename :: file:filename()) -> load_ret().
+load_abs(File) when is_list(File); is_atom(File) -> call({load_abs,File,[]}).
+
+%% XXX Filename is also an atom(), e.g. 'cover_compiled'
+-spec load_abs(Filename :: loaded_filename(), Module :: atom()) -> load_ret().
+load_abs(File,M) when (is_list(File) orelse is_atom(File)), is_atom(M) ->
+ call({load_abs,File,M}).
+
+%% XXX Filename is also an atom(), e.g. 'cover_compiled'
+-spec load_binary(Module :: atom(), Filename :: loaded_filename(), Binary :: binary()) -> load_ret().
+load_binary(Mod,File,Bin)
+ when is_atom(Mod), (is_list(File) orelse is_atom(File)), is_binary(Bin) ->
+ call({load_binary,Mod,File,Bin}).
+
+-spec load_native_partial(Module :: atom(), Binary :: binary()) -> load_ret().
+load_native_partial(Mod,Bin) when is_atom(Mod), is_binary(Bin) ->
+ call({load_native_partial,Mod,Bin}).
+
+-spec load_native_sticky(Module :: atom(), Binary :: binary(), WholeModule :: 'false' | binary()) -> load_ret().
+load_native_sticky(Mod,Bin,WholeModule)
+ when is_atom(Mod), is_binary(Bin),
+ (is_binary(WholeModule) orelse WholeModule =:= false) ->
+ call({load_native_sticky,Mod,Bin,WholeModule}).
+
+-spec delete(Module :: atom()) -> boolean().
+delete(Mod) when is_atom(Mod) -> call({delete,Mod}).
+
+-spec purge/1 :: (Module :: atom()) -> boolean().
+purge(Mod) when is_atom(Mod) -> call({purge,Mod}).
+
+-spec soft_purge(Module :: atom()) -> boolean().
+soft_purge(Mod) when is_atom(Mod) -> call({soft_purge,Mod}).
+
+-spec is_loaded(Module :: atom()) -> {'file', loaded_filename()} | 'false'.
+is_loaded(Mod) when is_atom(Mod) -> call({is_loaded,Mod}).
+
+-spec get_object_code(Module :: atom()) -> {atom(), binary(), file:filename()} | 'error'.
+get_object_code(Mod) when is_atom(Mod) -> call({get_object_code, Mod}).
+
+-spec all_loaded() -> [{atom(), loaded_filename()}].
+all_loaded() -> call(all_loaded).
+
+-spec stop() -> no_return().
+stop() -> call(stop).
+
+-spec root_dir() -> file:filename().
+root_dir() -> call({dir,root_dir}).
+
+-spec lib_dir() -> file:filename().
+lib_dir() -> call({dir,lib_dir}).
+
+%% XXX is_list() is for backwards compatibility -- take out in future version
+-spec lib_dir(App :: atom()) -> file:filename() | {'error', 'bad_name'}.
+lib_dir(App) when is_atom(App) ; is_list(App) -> call({dir,{lib_dir,App}}).
+
+-spec lib_dir(App :: atom(), SubDir :: atom()) -> file:filename() | {'error', 'bad_name'}.
+lib_dir(App, SubDir) when is_atom(App), is_atom(SubDir) -> call({dir,{lib_dir,App,SubDir}}).
+
+-spec compiler_dir() -> file:filename().
+compiler_dir() -> call({dir,compiler_dir}).
+
+%% XXX is_list() is for backwards compatibility -- take out in future version
+-spec priv_dir(Appl :: atom()) -> file:filename() | {'error', 'bad_name'}.
+priv_dir(App) when is_atom(App) ; is_list(App) -> call({dir,{priv_dir,App}}).
+
+-spec stick_dir(Directory :: file:filename()) -> 'ok' | 'error'.
+stick_dir(Dir) when is_list(Dir) -> call({stick_dir,Dir}).
+
+-spec unstick_dir(Directory :: file:filename()) -> 'ok' | 'error'.
+unstick_dir(Dir) when is_list(Dir) -> call({unstick_dir,Dir}).
+
+-spec stick_mod(Module :: atom()) -> 'true'.
+stick_mod(Mod) when is_atom(Mod) -> call({stick_mod,Mod}).
+
+-spec unstick_mod(Module :: atom()) -> 'true'.
+unstick_mod(Mod) when is_atom(Mod) -> call({unstick_mod,Mod}).
+
+-spec is_sticky(Module :: atom()) -> boolean().
+is_sticky(Mod) when is_atom(Mod) -> call({is_sticky,Mod}).
+
+-spec set_path(Directories :: [file:filename()]) -> 'true' | {'error', term()}.
+set_path(PathList) when is_list(PathList) -> call({set_path,PathList}).
+
+-spec get_path() -> [file:filename()].
+get_path() -> call(get_path).
+
+-spec add_path(Directory :: file:filename()) -> 'true' | {'error', term()}.
+add_path(Dir) when is_list(Dir) -> call({add_path,last,Dir}).
+
+-spec add_pathz(Directory :: file:filename()) -> 'true' | {'error', term()}.
+add_pathz(Dir) when is_list(Dir) -> call({add_path,last,Dir}).
+
+-spec add_patha(Directory :: file:filename()) -> 'true' | {'error', term()}.
+add_patha(Dir) when is_list(Dir) -> call({add_path,first,Dir}).
+
+-spec add_paths(Directories :: [file:filename()]) -> 'ok'.
+add_paths(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}).
+
+-spec add_pathsz(Directories :: [file:filename()]) -> 'ok'.
+add_pathsz(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}).
+
+-spec add_pathsa(Directories :: [file:filename()]) -> 'ok'.
+add_pathsa(Dirs) when is_list(Dirs) -> call({add_paths,first,Dirs}).
+
+%% XXX Contract's input argument differs from add_path/1 -- why?
+-spec del_path(Name :: file:filename() | atom()) -> boolean() | {'error', 'bad_name'}.
+del_path(Name) when is_list(Name) ; is_atom(Name) -> call({del_path,Name}).
+
+-type replace_path_error() :: {'error', 'bad_directory' | 'bad_name' | {'badarg',_}}.
+-spec replace_path(Name:: atom(), Dir :: file:filename()) -> 'true' | replace_path_error().
+replace_path(Name, Dir) when (is_atom(Name) or is_list(Name)) and
+ (is_atom(Dir) or is_list(Dir)) ->
+ call({replace_path,Name,Dir}).
+
+-spec rehash() -> 'ok'.
+rehash() -> call(rehash).
+
+%%-----------------------------------------------------------------
+
+call(Req) ->
+ code_server:call(code_server, Req).
+
+-spec start_link() -> {'ok', pid()} | {'error', 'crash'}.
+start_link() ->
+ start_link([stick]).
+
+-spec start_link(Flags :: [atom()]) -> {'ok', pid()} | {'error', 'crash'}.
+start_link(Flags) ->
+ do_start(Flags).
+
+%%-----------------------------------------------------------------
+%% In the init phase, code must not use any modules not yet loaded,
+%% either pre_loaded (e.g. init) or first in the script (e.g.
+%% erlang). Therefore, keep the modules used in init phase to a
+%% minimum, and make sure they are loaded before init is called.
+%% Try to call these modules from do_start instead.
+%% file is used in init - this is ok; file has been started before
+%% us, so the module is loaded.
+%%-----------------------------------------------------------------
+
+do_start(Flags) ->
+ %% The following module_info/1 calls are here to ensure
+ %% that the modules are loaded prior to their use elsewhere in
+ %% the code_server.
+ %% Otherwise a deadlock may occur when the code_server is starting.
+ code_server:module_info(module),
+ packages:module_info(module),
+ catch hipe_unified_loader:load_hipe_modules(),
+ gb_sets:module_info(module),
+ gb_trees:module_info(module),
+
+ ets:module_info(module),
+ os:module_info(module),
+ filename:module_info(module),
+ lists:module_info(module),
+
+ Mode = get_mode(Flags),
+ case init:get_argument(root) of
+ {ok,[[Root0]]} ->
+ Root = filename:join([Root0]), % Normalize. Use filename
+ case code_server:start_link([Root,Mode]) of
+ {ok,_Pid} = Ok2 ->
+ if
+ Mode =:= interactive ->
+ case lists:member(stick, Flags) of
+ true -> do_stick_dirs();
+ _ -> ok
+ end;
+ true ->
+ ok
+ end,
+ Ok2;
+ Other ->
+ Other
+ end;
+ Other ->
+ error_logger:error_msg("Can not start code server ~w ~n",[Other]),
+ {error, crash}
+ end.
+
+do_stick_dirs() ->
+ do_s(compiler),
+ do_s(stdlib),
+ do_s(kernel).
+
+do_s(Lib) ->
+ case lib_dir(Lib) of
+ {error, _} ->
+ ok;
+ Dir ->
+ %% The return value is intentionally ignored. Missing
+ %% directories is not a fatal error. (In embedded systems,
+ %% there is usually no compiler directory.)
+ stick_dir(filename:append(Dir, "ebin")),
+ ok
+ end.
+
+get_mode(Flags) ->
+ case lists:member(embedded, Flags) of
+ true ->
+ embedded;
+ _Otherwise ->
+ case init:get_argument(mode) of
+ {ok,[["embedded"]]} ->
+ embedded;
+ {ok,[["minimal"]]} ->
+ minimal;
+ _Else ->
+ interactive
+ end
+ end.
+
+%% Find out which version of a particular module we would
+%% load if we tried to load it, unless it's already loaded.
+%% In that case return the name of the file which contains
+%% the loaded object code
+
+-type which_ret_atoms() :: loaded_ret_atoms() | 'non_existing'.
+
+-spec which(Module :: atom()) -> file:filename() | which_ret_atoms().
+
+which(Module) when is_atom(Module) ->
+ case is_loaded(Module) of
+ false ->
+ which2(Module);
+ {file, File} ->
+ File
+ end.
+
+which2(Module) ->
+ Base = to_path(Module),
+ File = filename:basename(Base) ++ objfile_extension(),
+ Path = get_path(),
+ which(File, filename:dirname(Base), Path).
+
+-spec which(file:filename(), file:filename(), [file:filename()]) ->
+ 'non_existing' | file:filename().
+
+which(_, _, []) ->
+ non_existing;
+which(File, Base, [Directory|Tail]) ->
+ Path = if
+ Base =:= "." -> Directory;
+ true -> filename:join(Directory, Base)
+ end,
+ case erl_prim_loader:list_dir(Path) of
+ {ok,Files} ->
+ case lists:member(File,Files) of
+ true ->
+ filename:append(Path, File);
+ false ->
+ which(File, Base, Tail)
+ end;
+ _Error ->
+ which(File, Base, Tail)
+ end.
+
+%% Search the code path for a specific file. Try to locate
+%% it in the code path cache if possible.
+
+-spec where_is_file(Filename :: file:filename()) ->
+ 'non_existing' | file:filename().
+
+where_is_file(File) when is_list(File) ->
+ case call({is_cached,File}) of
+ no ->
+ Path = get_path(),
+ which(File, ".", Path);
+ Dir ->
+ filename:join(Dir, File)
+ end.
+
+-spec where_is_file(Path :: file:filename(), Filename :: file:filename()) ->
+ file:filename() | 'non_existing'.
+
+where_is_file(Path, File) when is_list(Path), is_list(File) ->
+ CodePath = get_path(),
+ if
+ Path =:= CodePath ->
+ case call({is_cached, File}) of
+ no ->
+ which(File, ".", Path);
+ Dir ->
+ filename:join(Dir, File)
+ end;
+ true ->
+ which(File, ".", Path)
+ end.
+
+-spec set_primary_archive(ArchiveFile :: file:filename(), ArchiveBin :: binary()) -> 'ok' | {'error', atom()}.
+
+set_primary_archive(ArchiveFile0, ArchiveBin) when is_list(ArchiveFile0), is_binary(ArchiveBin) ->
+ ArchiveFile = filename:absname(ArchiveFile0),
+ case call({set_primary_archive, ArchiveFile, ArchiveBin}) of
+ {ok, []} ->
+ ok;
+ {ok, _Mode, Ebins} ->
+ %% Prepend the code path with the ebins found in the archive
+ Ebins2 = [filename:join([ArchiveFile, E]) || E <- Ebins],
+ add_pathsa(Ebins2); % Returns ok
+ {error, _Reason} = Error ->
+ Error
+ end.
+
+%% Search the entire path system looking for name clashes
+
+-spec clash() -> 'ok'.
+
+clash() ->
+ Path = get_path(),
+ Struct = lists:flatten(build(Path)),
+ Len = length(search(Struct)),
+ io:format("** Found ~w name clashes in code paths ~n", [Len]).
+
+%% Internal for clash/0
+
+search([]) -> [];
+search([{Dir, File} | Tail]) ->
+ case lists:keyfind(File, 2, Tail) of
+ false ->
+ search(Tail);
+ {Dir2, File} ->
+ io:format("** ~s hides ~s~n",
+ [filename:join(Dir, File),
+ filename:join(Dir2, File)]),
+ [clash | search(Tail)]
+ end.
+
+build([]) -> [];
+build([Dir|Tail]) ->
+ Files = filter(objfile_extension(), Dir, file:list_dir(Dir)),
+ [decorate(Files, Dir) | build(Tail)].
+
+decorate([], _) -> [];
+decorate([File|Tail], Dir) ->
+ [{Dir, File} | decorate(Tail, Dir)].
+
+filter(_Ext, Dir, {error,_}) ->
+ io:format("** Bad path can't read ~s~n", [Dir]), [];
+filter(Ext, _, {ok,Files}) ->
+ filter2(Ext, length(Ext), Files).
+
+filter2(_Ext, _Extlen, []) -> [];
+filter2(Ext, Extlen,[File|Tail]) ->
+ case has_ext(Ext,Extlen, File) of
+ true -> [File | filter2(Ext, Extlen, Tail)];
+ false -> filter2(Ext, Extlen, Tail)
+ end.
+
+has_ext(Ext, Extlen,File) ->
+ L = length(File),
+ case catch lists:nthtail(L - Extlen, File) of
+ Ext -> true;
+ _ -> false
+ end.
+
+to_path(X) ->
+ filename:join(packages:split(X)).