File 2983-erts-Add-new-enif_select_error.patch of Package erlang

From 75d49429e0892c8747d3dbf43ac177c6cc807c18 Mon Sep 17 00:00:00 2001
From: Sverker Eriksson <sverker@erlang.org>
Date: Fri, 11 Dec 2020 19:03:06 +0100
Subject: [PATCH 3/3] erts: Add new enif_select_error

Return ERL_NIF_SELECT_NOTSUP if the underlying poll implementation
does not support error events (not Linux).
---
 erts/emulator/beam/atom.names                 |  1 +
 erts/emulator/beam/erl_drv_nif.h              |  3 +-
 erts/emulator/beam/erl_nif.h                  |  2 +
 erts/emulator/beam/erl_nif_api_funcs.h        |  3 +
 erts/emulator/sys/common/erl_check_io.c       | 73 +++++++++++++---
 erts/emulator/sys/common/erl_check_io.h       |  1 +
 erts/emulator/test/nif_SUITE.erl              | 85 ++++++++++++++++++-
 erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 26 +++++-
 8 files changed, 176 insertions(+), 18 deletions(-)

diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index a941534f3b..7febb56e28 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -561,6 +561,7 @@ atom re
 atom re_pattern
 atom re_run_trap
 atom read_concurrency
+atom ready_error
 atom ready_input
 atom ready_output
 atom reason
diff --git a/erts/emulator/beam/erl_drv_nif.h b/erts/emulator/beam/erl_drv_nif.h
index bf14d824b1..53d1a3a531 100644
--- a/erts/emulator/beam/erl_drv_nif.h
+++ b/erts/emulator/beam/erl_drv_nif.h
@@ -55,7 +55,8 @@ enum ErlNifSelectFlags {
     ERL_NIF_SELECT_WRITE     = (1 << 1),
     ERL_NIF_SELECT_STOP      = (1 << 2),
     ERL_NIF_SELECT_CANCEL    = (1 << 3),
-    ERL_NIF_SELECT_CUSTOM_MSG= (1 << 4)
+    ERL_NIF_SELECT_CUSTOM_MSG= (1 << 4),
+    ERL_NIF_SELECT_ERROR     = (1 << 5)
 };
 
 /*
diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h
index 5f25960053..1876193c6c 100644
--- a/erts/emulator/beam/erl_nif.h
+++ b/erts/emulator/beam/erl_nif.h
@@ -174,6 +174,8 @@ typedef int ErlNifEvent;
 #define ERL_NIF_SELECT_FAILED         (1 << 3)
 #define ERL_NIF_SELECT_READ_CANCELLED (1 << 4)
 #define ERL_NIF_SELECT_WRITE_CANCELLED (1 << 5)
+#define ERL_NIF_SELECT_ERROR_CANCELLED (1 << 6)
+#define ERL_NIF_SELECT_NOTSUP          (1 << 7)
 
 typedef enum
 {
diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h
index f719dfe868..d8debba6a5 100644
--- a/erts/emulator/beam/erl_nif_api_funcs.h
+++ b/erts/emulator/beam/erl_nif_api_funcs.h
@@ -642,6 +642,9 @@ static ERL_NIF_INLINE ERL_NIF_TERM enif_make_list9(ErlNifEnv* env,
 #  define enif_select_write(ENV, E, OBJ, PID, MSG, MSG_ENV) \
     enif_select_x(ENV, E, ERL_NIF_SELECT_WRITE | ERL_NIF_SELECT_CUSTOM_MSG, \
                   OBJ, PID, MSG, MSG_ENV)
+#  define enif_select_error(ENV, E, OBJ, PID, MSG, MSG_ENV) \
+    enif_select_x(ENV, E, ERL_NIF_SELECT_ERROR | ERL_NIF_SELECT_CUSTOM_MSG, \
+                  OBJ, PID, MSG, MSG_ENV)
 
 #if SIZEOF_LONG == 8
 #  define enif_get_int64 enif_get_long
diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c
index fb0b6699ef..02ecd66cbc 100644
--- a/erts/emulator/sys/common/erl_check_io.c
+++ b/erts/emulator/sys/common/erl_check_io.c
@@ -91,6 +91,7 @@ typedef enum {
 #else
     ERTS_EV_FLAG_FALLBACK      = ERTS_EV_FLAG_CLEAR,
 #endif
+    ERTS_EV_FLAG_WANT_ERROR    = 0x10,  /* ERL_NIF_SELECT_ERROR turned on */
 
     /* Combinations */
     ERTS_EV_FLAG_USED_FALLBACK = ERTS_EV_FLAG_USED | ERTS_EV_FLAG_FALLBACK,
@@ -366,6 +367,7 @@ alloc_nif_select_data(void)
 					     sizeof(ErtsNifSelectDataState));
     dsp->in.pid = NIL;
     dsp->out.pid = NIL;
+    dsp->err.pid = NIL;
     return dsp;
 }
 
@@ -717,6 +719,7 @@ deselect(ErtsDrvEventState *state, int mode)
         case ERTS_EV_TYPE_NIF:
             clear_select_event(&state->driver.nif->in);
             clear_select_event(&state->driver.nif->out);
+            clear_select_event(&state->driver.nif->err);
             enif_release_resource(state->driver.stop.resource->data);
             state->driver.stop.resource = NULL;
             break;
@@ -1092,7 +1095,7 @@ enif_select_x(ErlNifEnv* env,
         }
         on = 0;
         mode = ERL_DRV_READ | ERL_DRV_WRITE | ERL_DRV_USE;
-        ctl_events = ERTS_POLL_EV_IN | ERTS_POLL_EV_OUT;
+        ctl_events = ERTS_POLL_EV_IN | ERTS_POLL_EV_OUT | ERTS_POLL_EV_ERR;
         ctl_op = ERTS_POLL_OP_DEL;
     }
     else {
@@ -1104,6 +1107,14 @@ enif_select_x(ErlNifEnv* env,
         if (mode & ERL_DRV_WRITE) {
             ctl_events |= ERTS_POLL_EV_OUT;
         }
+        if (mode & ERL_NIF_SELECT_ERROR) {
+#if (!ERTS_ENABLE_KERNEL_POLL || ERTS_POLL_USE_EPOLL) && defined(ERTS_USE_POLL)
+            ctl_events |= ERTS_POLL_EV_ERR;
+#else
+            erts_mtx_unlock(fd_mtx(fd));
+            return INT_MIN | ERL_NIF_SELECT_NOTSUP;
+#endif
+        }
     }
 
     state = new_drv_ev_state(state,fd);
@@ -1152,6 +1163,8 @@ enif_select_x(ErlNifEnv* env,
         state->active_events |= ctl_events;
         if (state->type == ERTS_EV_TYPE_NONE)
             ctl_op = ERTS_POLL_OP_ADD;
+        if (ctl_events & ERTS_POLL_EV_ERR)
+            state->flags |= ERTS_EV_FLAG_WANT_ERROR;
     }
     else {
         ctl_events &= old_events;
@@ -1162,7 +1175,8 @@ enif_select_x(ErlNifEnv* env,
     if (ctl_events || ctl_op == ERTS_POLL_OP_DEL) {
         ErtsPollEvents new_events;
 
-        new_events = erts_io_control_wakeup(state, ctl_op,
+        new_events = erts_io_control_wakeup(state,
+                                            ctl_op,
                                             state->active_events,
                                             &wake_poller);
 
@@ -1172,6 +1186,7 @@ enif_select_x(ErlNifEnv* env,
                 state->flags = 0;
                 state->driver.nif->in.pid = NIL;
                 state->driver.nif->out.pid = NIL;
+                state->driver.nif->err.pid = NIL;
                 state->driver.stop.resource = NULL;
             }
             ret = INT_MIN | ERL_NIF_SELECT_FAILED;
@@ -1203,6 +1218,11 @@ enif_select_x(ErlNifEnv* env,
         if (mode & ERL_DRV_WRITE) {
             prepare_select_msg(&state->driver.nif->out, mode, recipient,
                                resource, msg, msg_env, am_ready_output);
+            msg_env = NULL;
+        }
+        if (mode & ERL_NIF_SELECT_ERROR) {
+            prepare_select_msg(&state->driver.nif->err, mode, recipient,
+                               resource, msg, msg_env, am_ready_error);
         }
         ret = 0;
     }
@@ -1219,6 +1239,11 @@ enif_select_x(ErlNifEnv* env,
                 clear_select_event(&state->driver.nif->out);
                 ret |= ERL_NIF_SELECT_WRITE_CANCELLED;
             }
+            if (mode & ERL_NIF_SELECT_ERROR
+                && is_not_nil(state->driver.nif->err.pid)) {
+                clear_select_event(&state->driver.nif->err);
+                ret |= ERL_NIF_SELECT_ERROR_CANCELLED;
+            }
         }
         if (mode & ERL_NIF_SELECT_STOP) {
             ASSERT(state->events==0);
@@ -1249,6 +1274,7 @@ enif_select_x(ErlNifEnv* env,
                 state->type = ERTS_EV_TYPE_STOP_NIF;
                 ret |= ERL_NIF_SELECT_STOP_SCHEDULED;
             }
+            state->flags &= ~ERTS_EV_FLAG_WANT_ERROR;
         }
         else
             ASSERT(mode & ERL_NIF_SELECT_CANCEL);
@@ -1371,8 +1397,9 @@ steal(erts_dsprintf_buf_t *dsbufp, ErtsDrvEventState *state, int mode)
 	break;
     }
     case ERTS_EV_TYPE_NIF: {
-        Eterm iid = state->driver.nif->in.pid;
-        Eterm oid = state->driver.nif->out.pid;
+        const Eterm iid = state->driver.nif->in.pid;
+        const Eterm oid = state->driver.nif->out.pid;
+        const Eterm eid = state->driver.nif->err.pid;
         const char* with = "with";
         ErlNifResourceType* rt = state->driver.stop.resource->type;
 
@@ -1384,6 +1411,10 @@ steal(erts_dsprintf_buf_t *dsbufp, ErtsDrvEventState *state, int mode)
         }
         if (is_not_nil(oid)) {
             erts_dsprintf(dsbufp, " %s out-pid %T", with, oid);
+            with = "and";
+        }
+        if (is_not_nil(eid)) {
+            erts_dsprintf(dsbufp, " %s err-pid %T", with, eid);
         }
         deselect(state, 0);
         erts_dsprintf(dsbufp, "\n");
@@ -1738,7 +1769,8 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
 
         DEBUG_PRINT_FD("triggered %s", state, ev2str(revents));
 
-        if (revents & ERTS_POLL_EV_ERR) {
+        if (revents & ERTS_POLL_EV_ERR
+            && !(state->flags & ERTS_EV_FLAG_WANT_ERROR)) {
             /*
              * Handle error events by triggering all in/out events
              * that has been selected on.
@@ -1812,13 +1844,14 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
 	}
 
         case ERTS_EV_TYPE_NIF: { /* Requested via enif_select()... */
-            struct erts_nif_select_event in = {NIL};
-            struct erts_nif_select_event out = {NIL};
+            struct erts_nif_select_event in_ev = {NIL};
+            struct erts_nif_select_event out_ev = {NIL};
+            struct erts_nif_select_event err_ev = {NIL};
 
-            if (revents & (ERTS_POLL_EV_IN|ERTS_POLL_EV_OUT)) {
+            if (revents & (ERTS_POLL_EV_IN | ERTS_POLL_EV_OUT | ERTS_POLL_EV_ERR)) {
                 if (revents & ERTS_POLL_EV_OUT) {
                     if (is_not_nil(state->driver.nif->out.pid)) {
-                        out = state->driver.nif->out;
+                        out_ev = state->driver.nif->out;
                         resource = state->driver.stop.resource;
                         state->driver.nif->out.pid = NIL;
                         state->driver.nif->out.mp = NULL;
@@ -1826,12 +1859,20 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
                 }
                 if (revents & ERTS_POLL_EV_IN) {
                     if (is_not_nil(state->driver.nif->in.pid)) {
-                        in = state->driver.nif->in;
+                        in_ev = state->driver.nif->in;
                         resource = state->driver.stop.resource;
                         state->driver.nif->in.pid = NIL;
                         state->driver.nif->in.mp = NULL;
                     }
                 }
+                if (revents & ERTS_POLL_EV_ERR) {
+                    if (is_not_nil(state->driver.nif->err.pid)) {
+                        err_ev = state->driver.nif->err;
+                        resource = state->driver.stop.resource;
+                        state->driver.nif->err.pid = NIL;
+                        state->driver.nif->err.mp = NULL;
+                    }
+                }
                 state->events &= ~revents;
             }
             else if (revents & ERTS_POLL_EV_NVAL) {
@@ -1841,11 +1882,14 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
 
             erts_mtx_unlock(fd_mtx(fd));
 
-            if (is_not_nil(in.pid)) {
-                send_select_msg(&in);
+            if (is_not_nil(in_ev.pid)) {
+                send_select_msg(&in_ev);
+            }
+            if (is_not_nil(out_ev.pid)) {
+                send_select_msg(&out_ev);
             }
-            if (is_not_nil(out.pid)) {
-                send_select_msg(&out);
+            if (is_not_nil(err_ev.pid)) {
+                send_select_msg(&err_ev);
             }
             continue;
         }
@@ -2724,6 +2768,7 @@ static int erts_debug_print_checkio_state(erts_dsprintf_buf_t *dsbufp,
 #endif
         erts_dsprintf(dsbufp, " inpid=%T", state->driver.nif->in.pid);
         erts_dsprintf(dsbufp, " outpid=%T", state->driver.nif->out.pid);
+        erts_dsprintf(dsbufp, " errpid=%T", state->driver.nif->err.pid);
         r = state->driver.stop.resource;
         erts_dsprintf(dsbufp, " resource=%p(%T:%T)", r, r->type->module, r->type->name);
     }
diff --git a/erts/emulator/sys/common/erl_check_io.h b/erts/emulator/sys/common/erl_check_io.h
index ac285119bc..b96f4f9609 100644
--- a/erts/emulator/sys/common/erl_check_io.h
+++ b/erts/emulator/sys/common/erl_check_io.h
@@ -148,6 +148,7 @@ struct erts_nif_select_event {
 typedef struct {
     struct erts_nif_select_event in;
     struct erts_nif_select_event out;
+    struct erts_nif_select_event err;
 } ErtsNifSelectDataState;
 
 #endif /* #ifndef ERL_CHECK_IO_INTERNAL__ */
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index df4876a294..147b15bb59 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -37,6 +37,7 @@
          t_call_nif_early/1,
          load_traced_nif/1,
          select/1, select_steal/1,
+	 select_error/1,
          monitor_process_a/1,
          monitor_process_b/1,
          monitor_process_c/1,
@@ -91,7 +92,7 @@ all() ->
     [{group, G} || G <- api_groups()]
         ++
     [reload_error, heap_frag, types, many_args,
-     select, select_steal,
+     {group, select},
      {group, monitor},
      monitor_frenzy,
      hipe,
@@ -135,7 +136,10 @@ groups() ->
                     monitor_process_c,
                     monitor_process_d,
                     monitor_process_purge,
-                    demonitor_process]}].
+                    demonitor_process]},
+     {select, [], [select,
+		   select_error,
+		   select_steal]}].
 
 api_groups() -> [api_latest, api_2_4, api_2_0].
 
@@ -639,6 +643,7 @@ load_traced_nif(Config) when is_list(Config) ->
 -define(ERL_NIF_SELECT_STOP, (1 bsl 2)).
 -define(ERL_NIF_SELECT_CANCEL, (1 bsl 3)).
 -define(ERL_NIF_SELECT_CUSTOM_MSG, (1 bsl 4)).
+-define(ERL_NIF_SELECT_ERROR, (1 bsl 5)).
 
 -define(ERL_NIF_SELECT_STOP_CALLED, (1 bsl 0)).
 -define(ERL_NIF_SELECT_STOP_SCHEDULED, (1 bsl 1)).
@@ -646,6 +651,8 @@ load_traced_nif(Config) when is_list(Config) ->
 -define(ERL_NIF_SELECT_FAILED, (1 bsl 3)).
 -define(ERL_NIF_SELECT_READ_CANCELLED, (1 bsl 4)).
 -define(ERL_NIF_SELECT_WRITE_CANCELLED, (1 bsl 5)).
+-define(ERL_NIF_SELECT_ERROR_CANCELLED, (1 bsl 6)).
+-define(ERL_NIF_SELECT_NOTSUP, (1 bsl 7)).
 
 select(Config) when is_list(Config) ->
     ensure_lib_loaded(Config),
@@ -781,6 +788,79 @@ receive_ready(_, Msg, _) ->
     [Got] = flush(),
     {true,_,_} = {Got=:=Msg, Got, Msg}.
 
+
+select_error(Config) when is_list(Config) ->
+    ensure_lib_loaded(Config),
+
+    case os:type() of
+	{unix,linux} ->
+	    select_error_do();
+	_ ->
+	    {skipped, "not Linux"}
+    end.
+
+select_error_do() ->
+    RefBin = list_to_binary(lists:duplicate(100, $x)),
+
+    select_error_do1(0, make_ref(), null),
+    select_error_do1(?ERL_NIF_SELECT_CUSTOM_MSG, [a, "list", RefBin], null),
+    select_error_do1(?ERL_NIF_SELECT_CUSTOM_MSG, [a, "list", RefBin], alloc_env),
+    ok.
+
+select_error_do1(Flag, Ref, MsgEnv) ->
+    {{R, _R_ptr}, {W, W_ptr}} = pipe_nif(),
+    ok = write_nif(W, <<"hej">>),
+    <<"hej">> = read_nif(R, 3),
+
+    %% Wait for error on write end when read end is closed
+    0 = select_nif(W, ?ERL_NIF_SELECT_ERROR bor Flag, W, null, Ref, MsgEnv),
+    [] = flush(0),
+    0 = close_nif(R),
+    receive_ready(W, Ref, ready_error),
+
+    check_stop_ret(select_nif(W, ?ERL_NIF_SELECT_STOP, W, null, Ref, null)),
+    [{fd_resource_stop, W_ptr, _}] = flush(),
+    {1, {W_ptr,_}} = last_fd_stop_call(),
+    true = is_closed_nif(W),
+    select_error_do2(Flag, Ref, MsgEnv).
+
+select_error_do2(Flag, Ref, MsgEnv) ->
+    {{R, _R_ptr}, {W, W_ptr}} = pipe_nif(),
+    ok = write_nif(W, <<"hej">>),
+    <<"hej">> = read_nif(R, 3),
+
+    %% Same again but test cancel of error works
+    0 = select_nif(W, ?ERL_NIF_SELECT_ERROR bor Flag, W, null, Ref, MsgEnv),
+    ?ERL_NIF_SELECT_ERROR_CANCELLED =
+	select_nif(W, ?ERL_NIF_SELECT_ERROR bor ?ERL_NIF_SELECT_CANCEL, W, null, Ref, null),
+    0 = close_nif(R),
+    [] = flush(0),
+    0 = select_nif(W, ?ERL_NIF_SELECT_ERROR bor Flag, W, null, Ref, MsgEnv),
+    receive_ready(W, Ref, ready_error),
+
+    check_stop_ret(select_nif(W, ?ERL_NIF_SELECT_STOP, W, null, Ref, null)),
+    [{fd_resource_stop, W_ptr, _}] = flush(),
+    {1, {W_ptr,_}} = last_fd_stop_call(),
+    true = is_closed_nif(W),
+    ok.
+
+-ifdef(NOT_DEFINED).
+check_select_error_supported() ->
+    {{_R, _R_ptr}, {W, W_ptr}} = pipe_nif(),
+    Ref = make_ref(),
+    case select_nif(W, ?ERL_NIF_SELECT_ERROR, W, null, Ref, null) of
+	0 ->
+	    check_stop_ret(select_nif(W, ?ERL_NIF_SELECT_STOP, W, null, Ref, null)),
+	    [{fd_resource_stop, W_ptr, _}] = flush(),
+	    {1, {W_ptr,_}} = last_fd_stop_call(),
+	    true = is_closed_nif(W),
+	    true;
+
+	Err when Err < 0, (Err band ?ERL_NIF_SELECT_NOTSUP) =/= 0 ->
+	    false
+    end.
+-endif.
+
 %% @doc The stealing child process for the select_steal test. Duplicates given
 %% W/RFds and runs select on them to steal
 select_steal_child_process(Parent, RFd) ->
@@ -3730,6 +3810,7 @@ dupe_resource_nif(_) -> ?nif_stub.
 pipe_nif() -> ?nif_stub.
 write_nif(_,_) -> ?nif_stub.
 read_nif(_,_) -> ?nif_stub.
+close_nif(_) -> ?nif_stub.
 is_closed_nif(_) -> ?nif_stub.
 clear_select_nif(_) -> ?nif_stub.
 last_fd_stop_call() -> ?nif_stub.
diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
index 93708fa99c..2c089b430c 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -2550,7 +2550,6 @@ static ERL_NIF_TERM select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
         ref_or_msg = enif_make_copy(msg_env, ref_or_msg);
     }
 
-    fdr->was_selected = 1;
     enif_self(env, &fdr->pid);
     switch (mode) {
     case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_READ:
@@ -2559,10 +2558,17 @@ static ERL_NIF_TERM select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
     case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_WRITE:
         retval = enif_select_write(env, fdr->fd, obj, pid, ref_or_msg, msg_env);
         break;
+    case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_ERROR:
+        retval = enif_select_error(env, fdr->fd, obj, pid, ref_or_msg, msg_env);
+        break;
     default:
         retval = enif_select(env, fdr->fd, mode, obj, pid, ref_or_msg);
     }
 
+    if (retval >= 0) {
+	fdr->was_selected = 1;
+    }
+
     if (msg_env)
         enif_free_env(msg_env);
 
@@ -2698,6 +2704,23 @@ static ERL_NIF_TERM read_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
     }
 }
 
+static ERL_NIF_TERM close_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+    struct fd_resource* fdr;
+    int ret;
+
+    if (!get_fd(env, argv[0], &fdr))
+        return enif_make_badarg(env);
+
+    assert(fdr->fd > 0);
+    assert(!fdr->was_selected);
+
+    ret = close(fdr->fd);
+    fdr->fd = -1;
+
+    return enif_make_int(env, ret);
+}
+
 static ERL_NIF_TERM is_closed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
 {
     struct fd_resource* fdr;
@@ -3729,6 +3752,7 @@ static ErlNifFunc nif_funcs[] =
     {"write_nif", 2, write_nif},
     {"dupe_resource_nif", 1, dupe_resource_nif},
     {"read_nif", 2, read_nif},
+    {"close_nif", 1, close_nif},
     {"is_closed_nif", 1, is_closed_nif},
     {"clear_select_nif", 1, clear_select_nif},
 #endif
-- 
2.26.2

openSUSE Build Service is sponsored by