%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*- % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod: %%% %%%------------------------------------------------------------------------ %%% @doc %%% ==CloudI OS Process Functionality== %%% @end %%% %%% MIT License %%% %%% Copyright (c) 2020-2021 Michael Truog %%% %%% Permission is hereby granted, free of charge, to any person obtaining a %%% copy of this software and associated documentation files (the "Software"), %%% to deal in the Software without restriction, including without limitation %%% the rights to use, copy, modify, merge, publish, distribute, sublicense, %%% and/or sell copies of the Software, and to permit persons to whom the %%% Software is furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %%% DEALINGS IN THE SOFTWARE. %%% %%% @author Michael Truog %%% @copyright 2020-2021 Michael Truog %%% @version 2.0.2 {@date} {@time} %%%------------------------------------------------------------------------ -module(cloudi_os_process). -author('mjtruog at protonmail dot com'). %% external interface -export([kill/2, kill_group/2, shell/1, shell/2, signal_to_integer/1, signal_to_string/1]). -type signal() :: sighup | sigint | sigquit | sigill | sigtrap | sigabrt | sigbus | sigfpe | sigkill | sigusr1 | sigsegv | sigusr2 | sigpipe | sigalrm | sigterm | sigstkflt | sigchld | sigcont | sigstop | sigtstp | sigttin | sigttou | sigurg | sigxcpu | sigxfsz | sigvtalrm | sigprof | sigwinch | sigio | sigpwr | sigsys | pos_integer(). -type signal_string() :: nonempty_list($A..$Z | $0..$9 | $#). -export_type([signal/0, signal_string/0]). -include("cloudi_core_i_constants.hrl"). -ifdef(CLOUDI_CORE_STANDALONE). -compile({nowarn_unused_function, [{os_spawn_kill_pids, 3}]}). -else. -compile({nowarn_unused_function, [{shell_ok, 1}, {ospids_to_iolist, 1}, {ospid_to_list, 1}, {ospgids_to_iolist, 1}, {ospgid_to_list, 1}]}). -endif. %%%------------------------------------------------------------------------ %%% External interface functions %%%------------------------------------------------------------------------ %%------------------------------------------------------------------------- %% @doc %% ===Kill OS processes.=== %% @end %%------------------------------------------------------------------------- -spec kill(Signal :: signal(), OSPids :: pos_integer() | list(pos_integer())) -> ok | {error, any()}. -ifdef(CLOUDI_CORE_STANDALONE). kill(Signal, OSPids) -> shell_ok(["kill -", erlang:integer_to_list(signal_to_integer(Signal)) | ospids_to_iolist(OSPids)]). -else. kill(Signal, OSPids) -> os_spawn_kill_pids(signal_to_integer(Signal), false, OSPids). -endif. %%------------------------------------------------------------------------- %% @doc %% ===Kill OS process groups.=== %% @end %%------------------------------------------------------------------------- -spec kill_group(Signal :: signal(), OSPids :: pos_integer() | list(pos_integer())) -> ok | {error, any()}. -ifdef(CLOUDI_CORE_STANDALONE). kill_group(Signal, OSPids) -> shell_ok(["kill -", erlang:integer_to_list(signal_to_integer(Signal)) | ospgids_to_iolist(OSPids)]). -else. kill_group(Signal, OSPids) -> os_spawn_kill_pids(signal_to_integer(Signal), true, OSPids). -endif. %%------------------------------------------------------------------------- %% @doc %% ===Execute a shell command.=== %% @end %%------------------------------------------------------------------------- -spec shell(Exec :: iodata()) -> {non_neg_integer(), list(binary())}. shell(Exec) -> Shell = erlang:open_port({spawn_executable, "/bin/sh"}, [{args, ["-"]}, {cd, "/"}, stream, binary, stderr_to_stdout, exit_status]), true = erlang:port_command(Shell, [Exec, "\nexit $?\n"]), shell_output(Shell, []). %%------------------------------------------------------------------------- %% @doc %% ===Execute a shell command with a string format.=== %% @end %%------------------------------------------------------------------------- -spec shell(Command :: string(), Arguments :: list()) -> {non_neg_integer(), list(binary())}. shell(Command, Arguments) -> shell(io_lib:format(Command, Arguments)). %%------------------------------------------------------------------------- %% @doc %% ===Convert a signal to an integer.=== %% @end %%------------------------------------------------------------------------- -spec signal_to_integer(Signal :: signal()) -> pos_integer(). signal_to_integer(sighup ) -> 1; signal_to_integer(sigint ) -> 2; signal_to_integer(sigquit ) -> 3; signal_to_integer(sigill ) -> 4; signal_to_integer(sigtrap ) -> 5; signal_to_integer(sigabrt ) -> 6; signal_to_integer(sigbus ) -> 7; signal_to_integer(sigfpe ) -> 8; signal_to_integer(sigkill ) -> 9; signal_to_integer(sigusr1 ) -> 10; signal_to_integer(sigsegv ) -> 11; signal_to_integer(sigusr2 ) -> 12; signal_to_integer(sigpipe ) -> 13; signal_to_integer(sigalrm ) -> 14; signal_to_integer(sigterm ) -> 15; signal_to_integer(sigstkflt) -> 16; signal_to_integer(sigchld ) -> 17; signal_to_integer(sigcont ) -> 18; signal_to_integer(sigstop ) -> 19; signal_to_integer(sigtstp ) -> 20; signal_to_integer(sigttin ) -> 21; signal_to_integer(sigttou ) -> 22; signal_to_integer(sigurg ) -> 23; signal_to_integer(sigxcpu ) -> 24; signal_to_integer(sigxfsz ) -> 25; signal_to_integer(sigvtalrm) -> 26; signal_to_integer(sigprof ) -> 27; signal_to_integer(sigwinch ) -> 28; signal_to_integer(sigio ) -> 29; signal_to_integer(sigpwr ) -> 30; signal_to_integer(sigsys ) -> 31; signal_to_integer(Signal) when is_integer(Signal), Signal > 0 -> Signal. %%------------------------------------------------------------------------- %% @doc %% ===Convert a signal integer to a string.=== %% Only signal integers that are consistent among all platforms use a %% specific string. %% @end %%------------------------------------------------------------------------- -spec signal_to_string(Signal :: pos_integer()) -> signal_string(). signal_to_string( 1) -> "SIGHUP"; signal_to_string( 2) -> "SIGINT"; signal_to_string( 3) -> "SIGQUIT"; signal_to_string( 4) -> "SIGILL"; signal_to_string( 5) -> "SIGTRAP"; signal_to_string( 6) -> "SIGABRT"; signal_to_string( 8) -> "SIGFPE"; signal_to_string( 9) -> "SIGKILL"; signal_to_string(11) -> "SIGSEGV"; signal_to_string(13) -> "SIGPIPE"; signal_to_string(14) -> "SIGALRM"; signal_to_string(15) -> "SIGTERM"; signal_to_string(Signal) when is_integer(Signal), Signal > 0 -> "SIG#" ++ erlang:integer_to_list(Signal). %%%------------------------------------------------------------------------ %%% Private functions %%%------------------------------------------------------------------------ shell_output(Shell, Output) -> receive {Shell, {data, Data}} -> shell_output(Shell, [Data | Output]); {Shell, {exit_status, Status}} -> {Status, lists:reverse(Output)} end. shell_ok(Exec) -> case shell(Exec) of {0, _} -> ok; {_, Output} -> {error, Output} end. os_spawn_kill_pids(SignalInteger, Group, OSPids) when is_integer(OSPids) -> os_spawn_kill_pids(SignalInteger, Group, [OSPids]); os_spawn_kill_pids(SignalInteger, Group, OSPids) when is_integer(SignalInteger), is_boolean(Group), is_list(OSPids) -> case supool:get(?OS_COMMAND_POOL) of CommandProcess when is_pid(CommandProcess) -> case cloudi_core_i_os_command:kill_pids(CommandProcess, SignalInteger, Group, OSPids) of {ok, ErrorString} -> if ErrorString == "" -> ok; is_list(ErrorString) -> {error, ErrorString} end; {error, _} = Error -> Error end; undefined -> {error, noproc} end. ospids_to_iolist(OSPids) when is_list(OSPids) -> [ospid_to_list(OSPid) || OSPid <- OSPids]; ospids_to_iolist(OSPids) -> ospid_to_list(OSPids). ospid_to_list(OSPid) when is_integer(OSPid), OSPid > 0 -> [$ | erlang:integer_to_list(OSPid)]. ospgids_to_iolist(OSPgids) when is_list(OSPgids) -> [ospgid_to_list(OSPgid) || OSPgid <- OSPgids]; ospgids_to_iolist(OSPgids) -> ospgid_to_list(OSPgids). ospgid_to_list(OSPgid) when is_integer(OSPgid), OSPgid > 1 -> [$ , $- | erlang:integer_to_list(OSPgid)].