%%%------------------------------------------------------------------- %%% File : ejabberd_update.erl %%% Author : Alexey Shchepin %%% Purpose : ejabberd code updater %%% Created : 27 Jan 2006 by Alexey Shchepin %%% %%% %%% ejabberd, Copyright (C) 2002-2024 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. %%% %%%------------------------------------------------------------------- -module(ejabberd_update). -author('alexey@process-one.net'). %% API -export([update/0, update/1, update_info/0]). -include("logger.hrl"). %%==================================================================== %% API %%==================================================================== %% Update all the modified modules update() -> case update_info() of {ok, Dir, UpdatedBeams, _Script, LowLevelScript, _Check} -> case eval_script( LowLevelScript, [], [{ejabberd, "", filename:join(Dir, "..")}]) of {ok, _} -> ?DEBUG("Updated: ~p~n", [UpdatedBeams]), {ok, UpdatedBeams}; Eval -> ?DEBUG("Eval: ~p~n", [Eval]), Eval end; {error, Reason} -> {error, Reason} end. %% Update only the specified modules update(ModulesToUpdate) -> case update_info() of {ok, Dir, UpdatedBeamsAll, _Script, _LowLevelScript, _Check} -> UpdatedBeamsNow = [A || A <- UpdatedBeamsAll, B <- ModulesToUpdate, A == B], {_, LowLevelScript, _} = build_script(Dir, UpdatedBeamsNow), case eval_script( LowLevelScript, [], [{ejabberd, "", filename:join(Dir, "..")}]) of {ok, _} -> ?DEBUG("Updated: ~p~n", [UpdatedBeamsNow]), {ok, UpdatedBeamsNow}; Eval -> ?DEBUG("Eval: ~p~n", [Eval]), Eval end; {error, Reason} -> {error, Reason} end. eval_script(Script, Apps, LibDirs) -> release_handler_1:eval_script(Script, Apps, LibDirs, [], []). %% Get information about the modified modules update_info() -> Dir = filename:dirname(code:which(ejabberd)), case file:list_dir(Dir) of {ok, Files} -> update_info(Dir, Files); {error, Reason} -> {error, Reason} end. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- update_info(Dir, Files) -> Beams = lists:sort(get_beams(Files)), UpdatedBeams = get_updated_beams(Beams), ?DEBUG("BEAM files: ~p~n", [UpdatedBeams]), {Script, LowLevelScript, Check} = build_script(Dir, UpdatedBeams), {ok, Dir, UpdatedBeams, Script, LowLevelScript, Check}. get_beams(Files) -> [list_to_atom(filename:rootname(FN)) || FN <- Files, lists:suffix(".beam", FN)]. %% Return only the beams that have different version get_updated_beams(Beams) -> lists:filter( fun(Module) -> NewVsn = get_new_version(Module), case code:is_loaded(Module) of {file, _} -> CurVsn = get_current_version(Module), (NewVsn /= CurVsn andalso NewVsn /= unknown_version); false -> false end end, Beams). get_new_version(Module) -> Path = code:which(Module), VersionRes = beam_lib:version(Path), case VersionRes of {ok, {Module, NewVsn}} -> NewVsn; %% If a m1.erl has -module("m2"): _ -> unknown_version end. get_current_version(Module) -> Attrs = Module:module_info(attributes), case lists:keysearch(vsn, 1, Attrs) of {value, {vsn, CurVsn}} -> CurVsn; _ -> unknown_version end. %% @spec(Dir::string(), UpdatedBeams::[atom()]) -> {Script,LowLevelScript,Check} build_script(Dir, UpdatedBeams) -> Script = make_script(UpdatedBeams), LowLevelScript = make_low_level_script(UpdatedBeams, Script), Check = release_handler_1:check_script( LowLevelScript, [{ejabberd, "", filename:join(Dir, "..")}]), Check1 = case Check of {ok, []} -> ?DEBUG("Script: ~p~n", [Script]), ?DEBUG("Low level script: ~p~n", [LowLevelScript]), ?DEBUG("Check: ~p~n", [Check]), ok; _ -> ?ERROR_MSG("Script: ~p~n", [Script]), ?ERROR_MSG("Low level script: ~p~n", [LowLevelScript]), ?ERROR_MSG("Check: ~p~n", [Check]), error end, {Script, LowLevelScript, Check1}. %% Copied from Erlang/OTP file: lib/sasl/src/systools.hrl -ifdef(SYSTOOLS_APP_DEF_WITHOUT_OPTIONAL). -record(application, {name, %% Name of the application, atom(). type = permanent, %% Application start type, atom(). vsn = "", %% Version of the application, string(). id = "", %% Id of the application, string(). description = "", %% Description of application, string(). modules = [], %% [Module | {Module,Vsn}] of modules %% incorporated in the application, %% Module = atom(), Vsn = string(). uses = [], %% [Application] list of applications required %% by the application, Application = atom(). includes = [], %% [Application] list of applications included %% by the application, Application = atom(). regs = [], %% [RegNames] a list of registered process %% names used by the application, RegNames = %% atom(). env = [], %% [{Key,Value}] environment variable of %% application, Key = Value = term(). maxT = infinity, %% Max time an application may exist, %% integer() | infinity. maxP = infinity, %% Max number of processes in an application, %% integer() | infinity. mod = [], %% [] | {Mod, StartArgs}, Mod= atom(), %% StartArgs = list(). start_phases, %% [{Phase, PhaseArgs}] | undefined, %% Phase = atom(), %% PhaseArgs = list(). dir = "" %% The directory where the .app file was %% found (internal use). }). -else. -record(application, {name, %% Name of the application, atom(). type = permanent, %% Application start type, atom(). vsn = "", %% Version of the application, string(). id = "", %% Id of the application, string(). description = "", %% Description of application, string(). modules = [], %% [Module | {Module,Vsn}] of modules %% incorporated in the application, %% Module = atom(), Vsn = string(). uses = [], %% [Application] list of applications required %% by the application, Application = atom(). optional = [], %% [Application] list of applications in uses %% that are optional, Application = atom(). includes = [], %% [Application] list of applications included %% by the application, Application = atom(). regs = [], %% [RegNames] a list of registered process %% names used by the application, RegNames = %% atom(). env = [], %% [{Key,Value}] environment variable of %% application, Key = Value = term(). maxT = infinity, %% Max time an application may exist, %% integer() | infinity. maxP = infinity, %% Max number of processes in an application, %% integer() | infinity. mod = [], %% [] | {Mod, StartArgs}, Mod= atom(), %% StartArgs = list(). start_phases, %% [{Phase, PhaseArgs}] | undefined, %% Phase = atom(), %% PhaseArgs = list(). dir = "" %% The directory where the .app file was %% found (internal use). }). -endif. make_script(UpdatedBeams) -> lists:map( fun(Module) -> {ok, {Module, [{attributes, NewAttrs}]}} = beam_lib:chunks(code:which(Module), [attributes]), CurAttrs = Module:module_info(attributes), case lists:keysearch(update_info, 1, NewAttrs) of {value, {_, [{update, _}]}} -> case lists:keysearch(update_info, 1, CurAttrs) of {value, {_, [{update, Extra}]}} -> {update, Module, {advanced, Extra}}; false -> {update, Module, {advanced, 0}} end; false -> {load_module, Module} end end, UpdatedBeams). make_low_level_script(UpdatedBeams, Script) -> EJDApp = #application{name = ejabberd, modules = UpdatedBeams}, {ok, LowLevelScript} = systools_rc:translate_scripts([Script], [EJDApp], [EJDApp]), LowLevelScript.