%%% @copyright 2012-2020 Michael Santos <michael.santos@gmail.com>
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%% 1. Redistributions of source code must retain the above copyright notice,
%%% this list of conditions and the following disclaimer.
%%%
%%% 2. Redistributions in binary form must reproduce the above copyright
%%% notice, this list of conditions and the following disclaimer in the
%%% documentation and/or other materials provided with the distribution.
%%%
%%% 3. Neither the name of the copyright holder nor the names of its
%%% contributors may be used to endorse or promote products derived from
%%% this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
%%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
%%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
%%% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
%%% HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
%%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
%%% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
%%% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
%%% LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
%%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
%%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-module(perc_signal).
-behaviour(gen_server).
-include_lib("perc/include/perc.hrl").
-include_lib("perc/include/perc_signal.hrl").
-export([
define/1,
signalfd_siginfo/1
]).
-export([
start/1,
stop/1,
getfd/1,
start_link/1
]).
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(state, {
port,
pid,
fd
}).
%%--------------------------------------------------------------------
%%% Exports
%%--------------------------------------------------------------------
start(Signals) when is_list(Signals) ->
start_link(Signals).
stop(Ref) when is_pid(Ref) ->
gen_server:call(Ref, stop).
getfd(Ref) when is_pid(Ref) ->
gen_server:call(Ref, getfd).
start_link(Signals) when is_list(Signals) ->
case perc:sigaddset(Signals) of
{ok, Mask} ->
Pid = self(),
gen_server:start_link(?MODULE, [Pid, Mask], []);
Err ->
Err
end.
%%--------------------------------------------------------------------
%%% Callbacks
%%--------------------------------------------------------------------
init([Pid, Mask]) ->
process_flag(trap_exit, true),
case perc:signalfd(Mask) of
{ok, FD} ->
Port = erlang:open_port({fd, FD, FD}, [stream, binary]),
{ok, #state{
port = Port,
pid = Pid,
fd = FD
}};
Err ->
Err
end.
handle_call(getfd, _From, #state{fd = FD} = State) ->
{reply, FD, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
%% Signal received
handle_info({Port, {data, Data}}, #state{port = Port, pid = Pid} = State) ->
_ = [
Pid ! {signal, self(), signalfd_siginfo(Signal)}
|| <<Signal:128/binary>> <= Data
],
{noreply, State};
% WTF?
handle_info(Info, State) ->
error_logger:error_report([wtf, Info]),
{noreply, State}.
terminate(_Reason, #state{fd = FD, port = Port}) ->
catch erlang:port_close(Port),
_ = perc:close(FD),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Utility functions
%%--------------------------------------------------------------------
% Signal definitions: linux
define(sighup) -> 1;
define(sigint) -> 2;
define(sigquit) -> 3;
define(sigill) -> 4;
define(sigtrap) -> 5;
define(sigabrt) -> 6;
define(sigbus) -> 7;
define(sigfpe) -> 8;
define(sigkill) -> 9;
define(sigusr1) -> 10;
define(sigsegv) -> 11;
define(sigusr2) -> 12;
define(sigpipe) -> 13;
define(sigalrm) -> 14;
define(sigterm) -> 15;
define(sigstkflt) -> 16;
define(sigchld) -> 17;
define(sigcont) -> 18;
define(sigstop) -> 19;
define(sigtstp) -> 20;
define(sigttin) -> 21;
define(sigttou) -> 22;
define(sigurg) -> 23;
define(sigxcpu) -> 24;
define(sigxfsz) -> 25;
define(sigvtalrm) -> 26;
define(sigprof) -> 27;
define(sigwinch) -> 28;
define(sigio) -> 29;
define(sigpwr) -> 30;
define(sigsys) -> 31;
define(sigrtmin) -> 34;
define(sigrtmax) -> 64.
signalfd_siginfo(
<<
% Signal number
?UINT32(Signo),
% Error number (unused)
?INT32(Errno),
% Signal code
?INT32(Code),
% PID of sender
?UINT32(Pid),
% Real UID of sender
?UINT32(Uid),
% File descriptor (SIGIO)
?INT32(Fd),
% Kernel timer ID (POSIX timers)
?UINT32(Tid),
% Band event (SIGIO)
?UINT32(Band),
% POSIX timer overrun count
?UINT32(Overrun),
% Trap number that caused signal
?UINT32(Trapno),
% Exit status or signal (SIGCHLD)
?INT32(Status),
% Integer sent by sigqueue(2)
?INT32(Int),
% Pointer sent by sigqueue(2)
?UINT64(Ptr),
% User CPU time consumed (SIGCHLD)
?UINT64(Utime),
% System CPU time consumed (SIGCHLD)
?UINT64(Stime),
% Address that generated signal (for hardware-generated signals)
?UINT64(Addr),
% Pad size to 128 bytes (allow for additional fields in the future)
Pad/binary
>> = Data
) when byte_size(Data) =:= 128 ->
#signalfd_siginfo{
ssi_signo = Signo,
ssi_errno = Errno,
ssi_code = Code,
ssi_pid = Pid,
ssi_uid = Uid,
ssi_fd = Fd,
ssi_tid = Tid,
ssi_band = Band,
ssi_overrun = Overrun,
ssi_trapno = Trapno,
ssi_status = Status,
ssi_int = Int,
ssi_ptr = Ptr,
ssi_utime = Utime,
ssi_stime = Stime,
ssi_addr = Addr,
pad = Pad
};
signalfd_siginfo(#signalfd_siginfo{
ssi_signo = Signo,
ssi_errno = Errno,
ssi_code = Code,
ssi_pid = Pid,
ssi_uid = Uid,
ssi_fd = Fd,
ssi_tid = Tid,
ssi_band = Band,
ssi_overrun = Overrun,
ssi_trapno = Trapno,
ssi_status = Status,
ssi_int = Int,
ssi_ptr = Ptr,
ssi_utime = Utime,
ssi_stime = Stime,
ssi_addr = Addr,
pad = Pad
}) ->
<<
% Signal number
?UINT32(Signo),
% Error number (unused)
?INT32(Errno),
% Signal code
?INT32(Code),
% PID of sender
?UINT32(Pid),
% Real UID of sender
?UINT32(Uid),
% File descriptor (SIGIO)
?INT32(Fd),
% Kernel timer ID (POSIX timers)
?UINT32(Tid),
% Band event (SIGIO)
?UINT32(Band),
% POSIX timer overrun count
?UINT32(Overrun),
% Trap number that caused signal
?UINT32(Trapno),
% Exit status or signal (SIGCHLD)
?INT32(Status),
% Integer sent by sigqueue(2)
?INT32(Int),
% Pointer sent by sigqueue(2)
?UINT64(Ptr),
% User CPU time consumed (SIGCHLD)
?UINT64(Utime),
% System CPU time consumed (SIGCHLD)
?UINT64(Stime),
% Address that generated signal (for hardware-generated signals)
?UINT64(Addr),
% Pad size to 128 bytes (allow for additional fields in the future)
Pad/binary
>>.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------