src/perc_signal.erl

%%% @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
%%--------------------------------------------------------------------