+ 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..e97bdf85a1f3 100644
--- a/erts/emulator/beam/erl_bif_port.c
+++ b/erts/emulator/beam/erl_bif_port.c
@@ -194,12 +194,12 @@ BIF_RETTYPE erts_internal_port_command_3(BIF_ALIST_3)
}
else {
Eterm ref;
+ Eterm cmd_error = THE_NON_VALUE;
#ifdef DEBUG
ref = NIL;
#endif
-
switch (erts_port_output(BIF_P, flags, prt, prt->common.id,
- BIF_ARG_2, &ref)) {
+ BIF_ARG_2, &ref, &cmd_error)) {
case ERTS_PORT_OP_BADARG:
case ERTS_PORT_OP_DROPPED:
ERTS_BIF_PREP_RET(res, am_badarg);
@@ -222,9 +222,16 @@ BIF_RETTYPE erts_internal_port_command_3(BIF_ALIST_3)
/* 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 (is_value(cmd_error)) {
+ Eterm *hp = HAlloc(BIF_P, 3);
+ ASSERT(is_atom(cmd_error));
+ result = TUPLE2(hp, am_error, cmd_error);
+ }
+ ERTS_BIF_PREP_RET(res, result);
break;
+ }
default:
ERTS_INTERNAL_ERROR("Unexpected erts_port_output() result");
break;
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..beb27134a621 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 *);
@@ -1006,7 +1009,7 @@ ErtsPortOpResult erts_port_command(Process *, int, Port *, Eterm, Eterm *);
/*
* Signals from processes to ports.
*/
-ErtsPortOpResult erts_port_output(Process *, int, Port *, Eterm, Eterm, Eterm *);
+ErtsPortOpResult erts_port_output(Process *, int, Port *, Eterm, Eterm, Eterm *, Eterm *);
ErtsPortOpResult erts_port_exit(Process *, int, Port *, Eterm, Eterm, Eterm *);
ErtsPortOpResult erts_port_connect(Process *, int, Port *, Eterm, Eterm, Eterm *);
ErtsPortOpResult erts_port_link(Process *, Port *, ErtsLink *, Eterm *);
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index dfdbe475b319..56aa1e06fe16 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -383,6 +383,8 @@ static Port *create_port(char *name,
prt->async_open_port = NULL;
prt->drv_data = (SWord) 0;
prt->os_pid = -1;
+ prt->caller = NIL;
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
/* Set default tracing */
erts_get_default_port_tracing(&ERTS_TRACE_FLAGS(prt), &ERTS_TRACER(prt));
@@ -651,7 +653,7 @@ erts_open_driver(erts_driver_t* driver, /* Pointer to driver. */
else
ERTS_OPEN_DRIVER_RET(NULL, -3, SYSTEM_LIMIT);
}
-
+
if (IS_TRACED_FL(port, F_TRACE_PORTS)) {
trace_port_open(port,
pid,
@@ -1406,6 +1408,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 +1419,24 @@ 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;
+ reply = am_true;
+ if (!bang_op) {
+ if (prt->cmd_error != ERTS_PORT_CMD_ERROR_ALLOWED) {
+ 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,25 @@ 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;
+ reply = am_true;
+ if (!bang_op) {
+ if (prt->cmd_error != ERTS_PORT_CMD_ERROR_ALLOWED) {
+ 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:
@@ -1699,7 +1725,8 @@ erts_port_output(Process *c_p,
Port *prt,
Eterm from,
Eterm list,
- Eterm *refp)
+ Eterm *refp,
+ Eterm *cmd_errorp)
{
ErtsPortOpResult res;
ErtsProc2PortSigData *sigdp = NULL;
@@ -1866,12 +1893,19 @@ erts_port_output(Process *c_p,
try_call_res = try_imm_drv_call(&try_call_state);
switch (try_call_res) {
case ERTS_TRY_IMM_DRV_CALL_OK:
+ if (cmd_errorp)
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_ALLOWED;
call_driver_outputv(flags & ERTS_PORT_SIG_FLG_BANG_OP,
c_p ? c_p->common.id : ERTS_INVALID_PID,
from,
prt,
drv,
&evp->driver);
+ if (cmd_errorp) {
+ if (prt->cmd_error != ERTS_PORT_CMD_ERROR_ALLOWED)
+ *cmd_errorp = prt->cmd_error;
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
+ }
if (force_immediate_call)
finalize_force_imm_drv_call(&try_call_state);
else
@@ -2033,6 +2067,8 @@ erts_port_output(Process *c_p,
try_call_res = try_imm_drv_call(&try_call_state);
switch (try_call_res) {
case ERTS_TRY_IMM_DRV_CALL_OK:
+ if (cmd_errorp)
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_ALLOWED;
call_driver_output(flags & ERTS_PORT_SIG_FLG_BANG_OP,
c_p ? c_p->common.id : ERTS_INVALID_PID,
from,
@@ -2040,6 +2076,11 @@ erts_port_output(Process *c_p,
drv,
buf,
size);
+ if (cmd_errorp) {
+ if (prt->cmd_error != ERTS_PORT_CMD_ERROR_ALLOWED)
+ *cmd_errorp = prt->cmd_error;
+ prt->cmd_error = ERTS_PORT_CMD_ERROR_NOT_ALLOWED;
+ }
if (force_immediate_call)
finalize_force_imm_drv_call(&try_call_state);
else
@@ -2164,6 +2205,8 @@ call_deliver_port_exit(int bang_op,
return ERTS_PORT_OP_DROPPED;
}
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+
if (broken_link) {
ErtsLink *lnk = erts_link_tree_lookup(ERTS_P_LINKS(prt), from);
if (!lnk)
@@ -2318,6 +2361,8 @@ set_port_connected(int bang_op,
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
return ERTS_PORT_OP_DROPPED;
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
+
if (bang_op) { /* Bang operation */
if (is_not_internal_pid(connect) || ERTS_PORT_GET_CONNECTED(prt) != from) {
send_badsig(prt);
@@ -2486,6 +2531,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 +2620,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 +2688,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 +2699,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);
@@ -2733,6 +2783,7 @@ port_monitor(Port *prt, erts_aint32_t state, ErtsMonitor *mon)
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP) {
port_monitor_failure(prt, prt->common.id, mon);
} else {
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
ASSERT(erts_monitor_is_target(mon));
erts_monitor_list_insert(&ERTS_P_LT_MONITORS(prt), mon);
}
@@ -2807,6 +2858,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 {
@@ -2821,8 +2873,10 @@ port_sig_demonitor(Port *prt, erts_aint32_t state, int op,
{
if (op == ERTS_PROC2PORT_SIG_EXEC)
port_demonitor(prt, state, sigdp->u.demonitor.mon);
- else
+ else {
+ ASSERT(prt->cmd_error == ERTS_PORT_CMD_ERROR_NOT_ALLOWED);
erts_monitor_release(sigdp->u.demonitor.mon);
+ }
return ERTS_PORT_REDS_DEMONITOR;
}
@@ -3987,7 +4041,8 @@ erts_port_command(Process *c_p,
} else if (is_tuple_arity(tp[2], 2)) {
tp = tuple_val(tp[2]);
if (tp[1] == am_command) {
- return erts_port_output(c_p, flags, port, cntd, tp[2], refp);
+ return erts_port_output(c_p, flags, port, cntd, tp[2],
+ refp, NULL);
}
else if (tp[1] == am_connect) {
flags &= ~ERTS_PORT_SIG_FLG_NOSUSPEND;
@@ -7715,6 +7770,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/unix/sys.c b/erts/emulator/sys/unix/sys.c
index 7ff8425d5229..61c015fcdd1b 100644
--- a/erts/emulator/sys/unix/sys.c
+++ b/erts/emulator/sys/unix/sys.c
@@ -448,7 +448,7 @@ prepare_crash_dump(int secs)
list = CONS(hp, make_small(8), list); hp += 2;
/* send to heart port, CMD = 8, i.e. prepare crash dump =o */
erts_port_output(NULL, ERTS_PORT_SIG_FLG_FORCE_IMM_CALL, heart_port,
- heart_port->common.id, list, NULL);
+ heart_port->common.id, list, NULL, NULL);
}
/* Make sure we have a fd for our crashdump file. */
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/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c
index 2a37c075f835..17e4f6971274 100644
--- a/erts/emulator/sys/win32/sys.c
+++ b/erts/emulator/sys/win32/sys.c
@@ -289,7 +289,7 @@ int erts_sys_prepare_crash_dump(int secs)
/* send to heart port, CMD = 8, i.e. prepare crash dump =o */
erts_port_output(NULL, ERTS_PORT_SIG_FLG_FORCE_IMM_CALL, heart_port,
- heart_port->common.id, list, NULL);
+ heart_port->common.id, list, NULL, NULL);
return 1;
}
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