+ Generate an
+
+ Returns zero on success and a non-zero integer on failure. +
+If
Where
Note that the
Do not send data to an unknown port. Any undefined behavior is
possible (including node crash) depending on how the port driver
@@ -5719,6 +5728,11 @@ receive_replies(ReqId, N, Acc) ->
driver of the port does not allow forcing through
a busy port.
+ Where Do not send data to an unknown port. Any undefined behavior is
diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c
index 737a6be15f15..4ac21b3b6f7b 100644
--- a/erts/emulator/beam/erl_bif_port.c
+++ b/erts/emulator/beam/erl_bif_port.c
@@ -197,6 +197,8 @@ BIF_RETTYPE erts_internal_port_command_3(BIF_ALIST_3)
#ifdef DEBUG
ref = NIL;
#endif
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_ALLOWED;
switch (erts_port_output(BIF_P, flags, prt, prt->common.id,
BIF_ARG_2, &ref)) {
@@ -209,6 +211,7 @@ BIF_RETTYPE erts_internal_port_command_3(BIF_ALIST_3)
if (flags & ERTS_PORT_SIG_FLG_NOSUSPEND)
ERTS_BIF_PREP_RET(res, am_false);
else {
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, prt);
ERTS_BIF_YIELD3(BIF_TRAP_EXPORT(BIF_erts_internal_port_command_3),
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
@@ -218,19 +221,29 @@ BIF_RETTYPE erts_internal_port_command_3(BIF_ALIST_3)
ASSERT(!(flags & ERTS_PORT_SIG_FLG_FORCE));
/* Fall through... */
case ERTS_PORT_OP_SCHEDULED:
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
ASSERT(is_internal_ordinary_ref(ref));
/* Signal order preserved by reply... */
BIF_RET(ref);
break;
- case ERTS_PORT_OP_DONE:
- ERTS_BIF_PREP_RET(res, am_true);
+ case ERTS_PORT_OP_DONE: {
+ Eterm result = am_true;
+ if (prt->cmd_error != ERTS_PORT_CMD_ERROR_ALLOWED) {
+ Eterm *hp = HAlloc(BIF_P, 3);
+ ASSERT(is_atom(prt->cmd_error));
+ result = TUPLE2(hp, am_error, prt->cmd_error);
+ }
+ ERTS_BIF_PREP_RET(res, result);
break;
+ }
default:
ERTS_INTERNAL_ERROR("Unexpected erts_port_output() result");
break;
}
}
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
+
state = erts_atomic32_read_acqb(&BIF_P->state);
if (state & ERTS_PSFLG_EXITING) {
KILL_CATCHES(BIF_P); /* Must exit */
diff --git a/erts/emulator/beam/erl_driver.h b/erts/emulator/beam/erl_driver.h
index 69f586254541..920c5fd1c49f 100644
--- a/erts/emulator/beam/erl_driver.h
+++ b/erts/emulator/beam/erl_driver.h
@@ -68,7 +68,7 @@
#define ERL_DRV_EXTENDED_MARKER (0xfeeeeeed)
#define ERL_DRV_EXTENDED_MAJOR_VERSION 3
-#define ERL_DRV_EXTENDED_MINOR_VERSION 3
+#define ERL_DRV_EXTENDED_MINOR_VERSION 4
/*
* The emulator will refuse to load a driver with a major version
@@ -622,6 +622,9 @@ EXTERN char *driver_dl_error(void);
EXTERN int erl_drv_putenv(const char *key, char *value);
EXTERN int erl_drv_getenv(const char *key, char *value, size_t *value_size);
+/* port_command() synchronous error... */
+EXTERN int erl_drv_command_error(ErlDrvPort dprt, char *string);
+
/* spawn start init ack */
EXTERN void erl_drv_init_ack(ErlDrvPort ix, ErlDrvData res);
diff --git a/erts/emulator/beam/erl_port.h b/erts/emulator/beam/erl_port.h
index ea8a488ea7b1..6066302a2874 100644
--- a/erts/emulator/beam/erl_port.h
+++ b/erts/emulator/beam/erl_port.h
@@ -151,6 +151,7 @@ struct _erl_drv_port {
erts_atomic_t run_queue;
erts_atomic_t connected; /* A connected process */
Eterm caller; /* Current caller. */
+ Eterm cmd_error;
erts_atomic_t data; /* Data associated with port. */
Uint bytes_in; /* Number of bytes read */
Uint bytes_out; /* Number of bytes written */
@@ -176,6 +177,8 @@ struct _erl_drv_port {
} *async_open_port; /* Reference used with async open port */
};
+#define ERTS_PORT_CMD_ERROR_NOT_ALLOWED THE_NON_VALUE
+#define ERTS_PORT_CMD_ERROR_ALLOWED NIL
void erts_init_port_data(Port *);
void erts_cleanup_port_data(Port *);
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index dfdbe475b319..52a41b8192db 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -651,7 +651,10 @@ erts_open_driver(erts_driver_t* driver, /* Pointer to driver. */
else
ERTS_OPEN_DRIVER_RET(NULL, -3, SYSTEM_LIMIT);
}
-
+
+ port->caller = NIL;
+ port->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
+
if (IS_TRACED_FL(port, F_TRACE_PORTS)) {
trace_port_open(port,
pid,
@@ -1406,6 +1409,7 @@ static int
port_sig_outputv(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
Eterm reply;
+ Eterm tmp_heap[3];
switch (op) {
case ERTS_PROC2PORT_SIG_EXEC:
@@ -1416,13 +1420,23 @@ port_sig_outputv(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *s
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
reply = am_badarg;
else {
- call_driver_outputv(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP,
+ int bang_op = sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP;
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+ if (!bang_op)
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_ALLOWED;
+ call_driver_outputv(bang_op,
sigdp->caller,
sigdp->u.outputv.from,
prt,
prt->drv_ptr,
sigdp->u.outputv.evp);
- reply = am_true;
+ if (bang_op || prt->cmd_error == ERTS_PORT_CMD_ERROR_ALLOWED)
+ reply = am_true;
+ else {
+ ASSERT(is_atom(prt->cmd_error));
+ reply = TUPLE2(&tmp_heap[0], am_error, prt->cmd_error);
+ }
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
}
break;
case ERTS_PROC2PORT_SIG_ABORT_NOSUSPEND:
@@ -1513,6 +1527,7 @@ static int
port_sig_output(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
Eterm reply;
+ Eterm tmp_heap[3];
switch (op) {
case ERTS_PROC2PORT_SIG_EXEC:
@@ -1523,14 +1538,24 @@ port_sig_output(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *si
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
reply = am_badarg;
else {
- call_driver_output(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP,
+ int bang_op = sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP;
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+ if (!bang_op)
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_ALLOWED;
+ call_driver_output(bang_op,
sigdp->caller,
sigdp->u.output.from,
prt,
prt->drv_ptr,
sigdp->u.output.bufp,
sigdp->u.output.size);
- reply = am_true;
+ if (bang_op || prt->cmd_error == ERTS_PORT_CMD_ERROR_ALLOWED)
+ reply = am_true;
+ else {
+ ASSERT(is_atom(prt->cmd_error));
+ reply = TUPLE2(&tmp_heap[0], am_error, prt->cmd_error);
+ }
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
}
break;
case ERTS_PROC2PORT_SIG_ABORT_NOSUSPEND:
@@ -2156,6 +2181,8 @@ call_deliver_port_exit(int bang_op,
* behave accordingly...
*/
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
return ERTS_PORT_OP_DROPPED;
@@ -2315,6 +2342,8 @@ set_port_connected(int bang_op,
* behave accordingly...
*/
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
return ERTS_PORT_OP_DROPPED;
@@ -2475,6 +2504,7 @@ erts_port_connect(Process *c_p,
static void
port_unlink_failure(Port *prt, Eterm port_id, ErtsSigUnlinkOp *sulnk)
{
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
erts_proc_sig_send_unlink_ack(prt ? &prt->common : NULL, port_id, sulnk);
}
@@ -2486,6 +2516,8 @@ port_unlink(Port *prt, erts_aint32_t state, ErtsSigUnlinkOp *sulnk)
} else {
ErtsILink *ilnk;
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+
ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(prt),
sulnk->from);
@@ -2573,6 +2605,7 @@ port_unlink_ack(Port *prt, erts_aint32_t state, ErtsSigUnlinkOp *sulnk)
port_unlink_ack_failure(prt->common.id, sulnk);
else {
ErtsILink *ilnk;
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(prt),
sulnk->from);
if (ilnk && ilnk->unlinking == sulnk->id) {
@@ -2640,6 +2673,7 @@ erts_port_unlink_ack(Process *c_p, Port *prt, ErtsSigUnlinkOp *sulnk)
static void
port_link_failure(Port *port, Eterm port_id, ErtsLink *lnk)
{
+ ASSERT(port->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
erts_proc_sig_send_link_exit(&port->common, port_id, lnk, am_noproc, NIL);
}
@@ -2650,6 +2684,7 @@ port_link(Port *prt, erts_aint32_t state, ErtsLink *nlnk)
port_link_failure(prt, prt->common.id, nlnk);
} else {
ErtsLink *lnk;
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
lnk = erts_link_tree_lookup_insert(&ERTS_P_LINKS(prt), nlnk);
if (lnk)
erts_link_release(nlnk);
@@ -2718,6 +2753,7 @@ static void
port_monitor_failure(Port *prt, Eterm port_id, ErtsMonitor *mon)
{
ASSERT(prt == NULL || prt->common.id == port_id);
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
erts_proc_sig_send_monitor_down(prt ? &prt->common : NULL, port_id,
mon, am_noproc);
}
@@ -2729,6 +2765,7 @@ static void
port_monitor(Port *prt, erts_aint32_t state, ErtsMonitor *mon)
{
ASSERT(prt);
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP) {
port_monitor_failure(prt, prt->common.id, mon);
@@ -2807,6 +2844,7 @@ port_demonitor(Port *port, erts_aint32_t state, ErtsMonitor *mon)
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
ASSERT(port && mon);
ASSERT(erts_monitor_is_origin(mon));
+ ASSERT(port->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
if (!erts_monitor_is_in_table(&mdp->u.target))
erts_monitor_release(mon);
else {
@@ -2819,6 +2857,7 @@ static int
port_sig_demonitor(Port *prt, erts_aint32_t state, int op,
ErtsProc2PortSigData *sigdp)
{
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
if (op == ERTS_PROC2PORT_SIG_EXEC)
port_demonitor(prt, state, sigdp->u.demonitor.mon);
else
@@ -7715,6 +7754,23 @@ erl_drv_getenv(const char *key, char *value, size_t *value_size)
}
}
+int
+erl_drv_command_error(ErlDrvPort dprt, char *string)
+{
+ Port *prt = erts_drvport2port(dprt);
+ if (prt == ERTS_INVALID_ERL_DRV_PORT)
+ return ESRCH;
+ if (!string)
+ return EINVAL;
+ if (prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED)
+ return EACCES;
+ prt->cmd_error = erts_atom_put((byte *) string,
+ sys_strlen(string),
+ ERTS_ATOM_ENC_LATIN1,
+ !0);
+ return 0;
+}
+
/* get heart_port
* used by erl_crash_dump
* - uses the fact that heart_port is registered when starting heart
diff --git a/erts/emulator/sys/win32/erl_win_dyn_driver.h b/erts/emulator/sys/win32/erl_win_dyn_driver.h
index 8105128f8710..75aa38cd2aa2 100644
--- a/erts/emulator/sys/win32/erl_win_dyn_driver.h
+++ b/erts/emulator/sys/win32/erl_win_dyn_driver.h
@@ -151,6 +151,8 @@ WDD_TYPEDEF(void, erl_drv_thread_exit, (void *resp));
WDD_TYPEDEF(int, erl_drv_thread_join, (ErlDrvTid, void **respp));
WDD_TYPEDEF(int, erl_drv_putenv, (const char *key, char *value));
WDD_TYPEDEF(int, erl_drv_getenv, (const char *key, char *value, size_t *value_size));
+WDD_TYPEDEF(int, erl_drv_command_error, (ErlDrvPort dprt, char *string));
+
typedef struct {
WDD_FTYPE(null_func) *null_func;
@@ -259,6 +261,7 @@ typedef struct {
WDD_FTYPE(erl_drv_thread_join) *erl_drv_thread_join;
WDD_FTYPE(erl_drv_putenv) *erl_drv_putenv;
WDD_FTYPE(erl_drv_getenv) *erl_drv_getenv;
+ WDD_FTYPE(erl_drv_command_error) *erl_drv_command_error;
/* Add new calls here */
} TWinDynDriverCallbacks;
@@ -379,6 +382,7 @@ extern TWinDynDriverCallbacks WinDynDriverCallbacks;
#define erl_drv_thread_join (WinDynDriverCallbacks.erl_drv_thread_join)
#define erl_drv_putenv (WinDynDriverCallbacks.erl_drv_putenv)
#define erl_drv_getenv (WinDynDriverCallbacks.erl_drv_getenv)
+#define erl_drv_command_error (WinDynDriverCallbacks.erl_drv_command_error)
/* The only variable in the interface... */
#define driver_term_nil (driver_mk_term_nil())
@@ -510,6 +514,7 @@ do { \
((W).erl_drv_thread_join) = erl_drv_thread_join; \
((W).erl_drv_putenv) = erl_drv_putenv; \
((W).erl_drv_getenv) = erl_drv_getenv; \
+((W).erl_drv_command_error) = erl_drv_command_error; \
} while (0)
diff --git a/erts/emulator/test/driver_SUITE.erl b/erts/emulator/test/driver_SUITE.erl
index 1a512e054490..ebf2f3cf4ca8 100644
--- a/erts/emulator/test/driver_SUITE.erl
+++ b/erts/emulator/test/driver_SUITE.erl
@@ -84,6 +84,7 @@
env/1,
poll_pipe/1,
lots_of_used_fds_on_boot/1,
+ command_error/1,
z_test/1]).
-export([bin_prefix/2]).
@@ -149,6 +150,7 @@ all() -> %% Keep a_test first and z_test last...
consume_timeslice,
env,
poll_pipe,
+ command_error,
z_test].
groups() ->
@@ -237,7 +239,7 @@ init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
0 = element(1, CIOD),
[{testcase, Case}|Config].
-end_per_testcase(Case, Config) ->
+end_per_testcase(_Case, Config) ->
%% Logs some info about the system
ct_os_cmd("epmd -names"),
ct_os_cmd("ps aux"),
@@ -2498,6 +2500,56 @@ env_notfound_test(Port, Key) ->
true = os:unsetenv(Key),
[255] = env_control(Port, 1, Key, "").
+command_error(Config) when is_list(Config) ->
+ erl_ddll:start(),
+
+ ODrv = command_error_output_drv,
+ ok = load_driver(proplists:get_value(data_dir, Config), ODrv),
+ Port0 = open_port({spawn, ODrv}, [{parallelism, false}]),
+ ok = command_error_test(Port0),
+ Port1 = open_port({spawn, ODrv}, [{parallelism, true}]),
+ ok = command_error_test(Port1),
+ ok = erl_ddll:unload_driver(ODrv),
+
+ OVDrv = command_error_outputv_drv,
+ ok = load_driver(proplists:get_value(data_dir, Config), OVDrv),
+ Port2 = open_port({spawn, OVDrv}, [{parallelism, false}]),
+ ok = command_error_test(Port2),
+ Port3 = open_port({spawn, OVDrv}, [{parallelism, true}]),
+ ok = command_error_test(Port3),
+ ok = erl_ddll:unload_driver(OVDrv).
+
+command_error_test(Port) ->
+ [] = port_control(Port, 0, ""),
+ Port ! {self(), {command, "user_error_1"}},
+ Port ! {self(), {command, "user_error_2"}},
+ try
+ port_command(Port, "user_error_1"),
+ error(unexpected_return)
+ catch
+ error:user_error_1 -> ok
+ end,
+ try
+ port_command(Port, "user_error_2"),
+ error(unexpected_return)
+ catch
+ error:user_error_2 -> ok
+ end,
+ try
+ port_command(Port, "user_error_1", [nosuspend]),
+ error(unexpected_return)
+ catch
+ error:user_error_1 -> ok
+ end,
+ try
+ port_command(Port, "user_error_2", [nosuspend]),
+ error(unexpected_return)
+ catch
+ error:user_error_2 -> ok
+ end,
+ port_close(Port),
+ ok.
+
a_test(Config) when is_list(Config) ->
rpc(Config, fun check_io_debug/0).
diff --git a/erts/emulator/test/driver_SUITE_data/Makefile.src b/erts/emulator/test/driver_SUITE_data/Makefile.src
index 77cbd34fb128..4436178a00cd 100644
--- a/erts/emulator/test/driver_SUITE_data/Makefile.src
+++ b/erts/emulator/test/driver_SUITE_data/Makefile.src
@@ -21,7 +21,9 @@ MISC_DRVS = outputv_drv@dll@ \
async_blast_drv@dll@ \
thr_msg_blast_drv@dll@ \
consume_timeslice_drv@dll@ \
- env_drv@dll@
+ env_drv@dll@ \
+ command_error_output_drv@dll@ \
+ command_error_outputv_drv@dll@
SYS_INFO_DRVS = sys_info_base_drv@dll@ \
sys_info_prev_drv@dll@ \
diff --git a/erts/emulator/test/driver_SUITE_data/command_error_drv.c b/erts/emulator/test/driver_SUITE_data/command_error_drv.c
new file mode 100644
index 000000000000..c1ed6dc8469c
--- /dev/null
+++ b/erts/emulator/test/driver_SUITE_data/command_error_drv.c
@@ -0,0 +1,150 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2023. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+#if defined(__WIN32__)
+#include