diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml index 2547b6e952cf..d3e93c15c5b4 100644 --- a/erts/doc/src/erl_driver.xml +++ b/erts/doc/src/erl_driver.xml @@ -2049,6 +2049,39 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]> + + int + erl_drv_command_error(ErlDrvPort port, + char *error_reason) + Generate an error exception in a port_command() call + +

+ Generate an error exception, with the reason + error_reason as an atom, in a call to the + + erlang:port_command/2 BIF or the + + erlang:port_command/3 BIF. The error can only be + generated from the drivers + outputv() or + output() callbacks, which implements port command, + when the port command has been initiated via one of the synchronous + port_command() BIFs. When the port command has been + initiated by the asynchronous Port ! {Owner, {command, Data}} + construct, this call will fail and no exception will be + generated. +

+

+ error_reason needs to be a null terminated latin1 string. If + the string is longer than 255 characters (excluding the null + termination character), it will be truncated to 255 characters. +

+

+ Returns zero on success and a non-zero integer on failure. +

+
+
+ voiderl_drv_cond_broadcast(ErlDrvCond *cnd) diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index ceb85415c1c1..45730952d293 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -5655,7 +5655,16 @@ receive_replies(ReqId, N, Acc) ->

If Data is an invalid I/O list.

+ Error + +

Where Error is an atom defined by the driver code + executed by the port.

+
+

Note that the Port ! {PortOwner, {command, Data}} + construct only can fail with a badarg error + exception if Port is an atom which is not registered as a port + or a process.

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. + Error + +

Where Error is an atom defined by the driver code + executed by the port.

+

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 +#else +#include +#endif /* UNIX */ +#include +#include +#include "erl_driver.h" + +static ErlDrvData start(ErlDrvPort port, + char *command); +#ifndef USE_OUTPUTV +static void output(ErlDrvData drv_data, + char *buf, ErlDrvSizeT len); +#else +static void outputv(ErlDrvData drv_data, + ErlIOVec *ev); +#endif +static ErlDrvSSizeT control(ErlDrvData drv_data, + unsigned int command, char *buf, + ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen); + +static void chk_cmd_error_eacces(ErlDrvPort port); +static void chk_cmd_error_success_or_eacces(ErlDrvPort port, char *err, int len); + +static ErlDrvEntry command_error_drv_entry = { + NULL /* init */, + start, + NULL /* stop */, +#ifndef USE_OUTPUTV + output, +#else + NULL, +#endif + NULL /* ready_input */, + NULL /* ready_output */, + COMMAND_ERROR_DRV_NAME, + NULL /* finish */, + NULL /* handle */, + control, + NULL /* timeout */, +#ifdef USE_OUTPUTV + outputv, +#else + NULL, +#endif + NULL /* ready_async */, + NULL /* flush */, + NULL /* call */, + NULL /* event */, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL /* handle2 */, + NULL /* handle_monitor */ +}; + +DRIVER_INIT(command_error_drv) +{ + return &command_error_drv_entry; +} + +static ErlDrvData +start(ErlDrvPort port, char *command) +{ + chk_cmd_error_eacces(port); + return (ErlDrvData) port; +} + +#ifndef USE_OUTPUTV + +static void +output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len) +{ + ErlDrvPort port = (ErlDrvPort) drv_data; + chk_cmd_error_success_or_eacces(port, buf, len); +} + +#else + +static void +outputv(ErlDrvData drv_data, ErlIOVec *ev) +{ + char buf[255]; + int vix, len = 0; + ErlDrvPort port = (ErlDrvPort) drv_data; + for (vix = 0; vix < ev->vsize; vix++) { + SysIOVec *iovp = &ev->iov[vix]; + memcpy(&buf[len], iovp->iov_base, iovp->iov_len); + len += iovp->iov_len; + } + chk_cmd_error_success_or_eacces(port, buf, len); +} +#endif + +static ErlDrvSSizeT +control(ErlDrvData drv_data, + unsigned int command, char *buf, + ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen) +{ + ErlDrvPort port = (ErlDrvPort) drv_data; + chk_cmd_error_eacces(port); + *rbuf = NULL; + return 0; +} + +static void +chk_cmd_error_eacces(ErlDrvPort port) +{ + int res = erl_drv_command_error(port, "fail"); + if (res == 0) + driver_failure_atom(port, "unexpected_success"); + else if (res != EACCES) + driver_failure_atom(port, "unexpected_failure"); +} + +static void +chk_cmd_error_success_or_eacces(ErlDrvPort port, char *err, int len) +{ + int res; + char error[256]; + if (len > 255) + len = 255; + memcpy(error, err, len); + error[len] = '\0'; + res = erl_drv_command_error(port, error); + if (res != 0 && res != EACCES) + driver_failure_atom(port, "unexpected_failure"); +} diff --git a/erts/emulator/test/driver_SUITE_data/command_error_output_drv.c b/erts/emulator/test/driver_SUITE_data/command_error_output_drv.c new file mode 100644 index 000000000000..1476ee5d2541 --- /dev/null +++ b/erts/emulator/test/driver_SUITE_data/command_error_output_drv.c @@ -0,0 +1,22 @@ +/* + * %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% + */ +#define COMMAND_ERROR_DRV_NAME "command_error_output_drv" +#undef USE_OUTPUTV +#include "command_error_drv.c" diff --git a/erts/emulator/test/driver_SUITE_data/command_error_outputv_drv.c b/erts/emulator/test/driver_SUITE_data/command_error_outputv_drv.c new file mode 100644 index 000000000000..62454ad38d48 --- /dev/null +++ b/erts/emulator/test/driver_SUITE_data/command_error_outputv_drv.c @@ -0,0 +1,22 @@ +/* + * %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% + */ +#define COMMAND_ERROR_DRV_NAME "command_error_outputv_drv" +#define USE_OUTPUTV +#include "command_error_drv.c" diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam index 63d08c461456..dde4e1fbcc6e 100644 Binary files a/erts/preloaded/ebin/erlang.beam and b/erts/preloaded/ebin/erlang.beam differ diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 41471e51e28f..7508b371c958 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -3584,7 +3584,7 @@ port_command(Port, Data) -> Res -> Res end of true -> true; - Error -> error_with_info(Error, [Port, Data]) + Error -> error_with_info(unwrap_err_tpl(Error), [Port, Data]) end. -spec port_command(Port, Data, OptionList) -> boolean() when @@ -3600,9 +3600,12 @@ port_command(Port, Data, Flags) -> end of badopt -> badarg_with_cause([Port, Data, Flags], badopt); Bool when erlang:is_boolean(Bool) -> Bool; - Error -> error_with_info(Error, [Port, Data, Flags]) + Error -> error_with_info(unwrap_err_tpl(Error), [Port, Data, Flags]) end. +unwrap_err_tpl({error, Error}) -> Error; +unwrap_err_tpl(Error) -> Error. + -spec port_connect(Port, Pid) -> 'true' when Port :: port() | atom(), Pid :: pid().