summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjell Winblad <kjellwinblad@gmail.com>2021-03-11 13:13:05 +0100
committerKjell Winblad <kjellwinblad@gmail.com>2021-04-06 10:58:10 +0200
commit4bfb707e60c2b2b34b2116102c4f431a09313e9e (patch)
treedf80536f7b623c25d2f5ed4862a14a9d8a5fc497
parentb7e8e044d61afd30c0f4ef048b74a6d567a20dd7 (diff)
downloaderlang-4bfb707e60c2b2b34b2116102c4f431a09313e9e.tar.gz
erl_call: Add -fetch_stdout and -no_result_term options
This commit adds two options to the erl_call command line tool: * -fetch_stdout Executes the given code (provided by the -a or -e options) in a new process with a group leader that forwards all stdout (standard output) data so that it is printed to stdout of the erl_call process. This means that stdout data that is written during the execution of the called code, by the code and by descendant processes, will be forwarded (given that the group leader has not been changed by a call to erlang:group_leader/2). The printed data is UTF-8 encoded. * -fetch_stdout This option disables printing of the result term. In order to implement the first of these two options a new function called ei_xrpc_from has been added to erl_interface. This function is documented in the erl_interface documentation.
-rw-r--r--lib/erl_interface/doc/src/ei_connect.xml109
-rw-r--r--lib/erl_interface/doc/src/erl_call_cmd.xml51
-rw-r--r--lib/erl_interface/include/ei.h5
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c52
-rw-r--r--lib/erl_interface/src/prog/erl_call.c136
-rw-r--r--lib/erl_interface/test/erl_call_SUITE.erl117
-rw-r--r--lib/kernel/src/rpc.erl139
7 files changed, 558 insertions, 51 deletions
diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml
index c5ef9440c5..cd9465bb18 100644
--- a/lib/erl_interface/doc/src/ei_connect.xml
+++ b/lib/erl_interface/doc/src/ei_connect.xml
@@ -1047,6 +1047,7 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<func>
<name since=""><ret>int</ret><nametext>ei_rpc(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, ei_x_buff *x)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name>
+ <name since="OTP-17048"><ret>int</ret><nametext>ei_xrpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, int flags)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name>
<fsummary>Remote Procedure Call from C to Erlang.</fsummary>
<type>
@@ -1055,13 +1056,27 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
</type>
<desc>
- <p>Supports calling Erlang functions on remote nodes.
- <c>ei_rpc_to()</c> sends an RPC request to a remote node
- and <c>ei_rpc_from()</c> receives the results of such a
- call. <c>ei_rpc()</c> combines the functionality of these
- two functions by sending an RPC request and waiting for the results.
- See also <seemfa marker="kernel:rpc#call/4">
- <c>rpc:call/4</c></seemfa> in Kernel.</p>
+ <p>
+ Supports calling Erlang functions on remote nodes.
+ <c>ei_rpc_to()</c> sends an RPC request to a remote node and
+ <c>ei_rpc_from()</c> receives the results of such a
+ call. <c>ei_rpc()</c> combines the functionality of these two
+ functions by sending an RPC request and waiting for the
+ results.
+ </p>
+ <p>
+ The <c>ei_xrpc_to()</c> function is equivalent to
+ <c>ei_rpc_to()</c> when its <c>flags</c> parameter is set to
+ <c>0</c>. When the flags parameter of <c>ei_xrpc_to()</c> is
+ set to <c>EI_RPC_FETCH_STDOUT</c>, stdout (standard output)
+ data are forwarded. See the documentation for the flags
+ parameter for more information about the
+ <c>EI_RPC_FETCH_STDOUT</c> flag.
+ </p>
+ <p>
+ <seemfa marker="kernel:rpc#call/4"><c>rpc:call/4</c></seemfa>
+ in Kernel.
+ </p>
<list type="bulleted">
<item>
<p><c>ec</c> is the C-node structure previously
@@ -1104,21 +1119,75 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<c>ei_receive_msg</c></seecref>.</p>
</item>
<item>
- <p><c>x</c> points to the dynamic buffer that receives
- the result. For <c>ei_rpc()</c> this is the result
- without the version magic number. For
- <c>ei_rpc_from()</c> the result returns a version
- magic number and a 2-tuple <c>{rex,Reply}</c>.</p>
+ <p><c>x</c> points to the dynamic buffer that receives the
+ result. For <c>ei_rpc()</c> this is the result without the
+ version magic number. For an <c>ei_rpc_from()</c> call the
+ result consists of a version magic number and a 2-tuple.
+ The 2-tuple can be in one of the following two forms:</p>
+ <taglist>
+ <tag><c>{rex,Reply}</c></tag>
+ <item>
+ This response value means that the RPC has
+ completed. The result value is the <c>Reply</c>
+ term. This is the only type of response that one can
+ get from an RPC triggered by a call to
+ <c>ei_rpc_to()</c> or <c>ei_xrpc_to()</c> without the
+ <c>EI_RPC_FETCH_STDOUT</c> flag. If the RPC was
+ triggered by a call to <c>ei_xrpc_to()</c> with the
+ <c>EI_RPC_FETCH_STDOUT</c> flag set, then all forwarded
+ stdout data has been received.
+ </item>
+ <tag><c>{rex_stdout,StdOutUTF8Binary}</c></tag>
+ <item>
+ This response value can only be obtained if the RPC
+ call was triggered by a call to <c>ei_xrpc_to()</c>
+ with the <c>EI_RPC_FETCH_STDOUT</c> flag set. This
+ response value means that forwarded stdout data has
+ been received. The stdout data is stored in a binary
+ and is UTF-8 encoded. One may need to call
+ <c>ei_rpc_from()</c> multiple times to read all the
+ stdout data. The stdout data is received in the same
+ order as it was written. All forwarded stdout data have
+ been received when a <c>{rex,Reply}</c> tuple has been
+ obtained from an <c>ei_rpc_from()</c> call.
+ </item>
+ </taglist>
+ </item>
+ <item>
+ <p><c>flags</c> The flag <c>EI_RPC_FETCH_STDOUT</c> is
+ currently the only flag that is supported by
+ <c>ei_xrpc_to()</c>. When <c>EI_RPC_FETCH_STDOUT</c> is
+ set, the called function is executed in a new process with
+ a <seemfa marker="erts:erlang#group_leader/0">group
+ leader</seemfa> that forwards all stdout data. This means
+ that stdout data that are written during the execution of
+ the called function, by the called function and by
+ descendant processes, will be forwarded (given that the
+ group leader has not been changed by a call to <seemfa
+ marker="erts:erlang#group_leader/2"><c>erlang:group_leader/2</c></seemfa>).
+ The forwarded stdout data need to be collected by a
+ sequence of calls to <c>ei_rpc_from()</c>. See the
+ description of the <c>x</c> parameter for how
+ <c>ei_rpc_from()</c> is used to receive stdout data. See
+ the documentation of the <seeguide
+ marker="stdlib:io_protocol">the I/O protocol</seeguide>,
+ for more information about the group leader concept.</p>
+ <note>
+ <p>
+ The flag <c>EI_RPC_FETCH_STDOUT</c> only works when
+ interacting with a node with a version greater or
+ equal to OTP-24.
+ </p>
+ </note>
</item>
</list>
- <p><c>ei_rpc()</c> returns the number of bytes in the
- result on success and <c>-1</c> on failure.
- <c>ei_rpc_from()</c> returns the
- number of bytes, otherwise one of <c>ERL_TICK</c>,
- <c>ERL_TIMEOUT</c>,
- and <c>ERL_ERROR</c>. When failing, all three
- functions set <c>erl_errno</c> to one of the
- following:</p>
+ <p><c>ei_rpc()</c> returns the number of bytes in the result
+ on success and <c>-1</c> on failure. <c>ei_rpc_from()</c>
+ returns the number of bytes, otherwise one of <c>ERL_TICK</c>,
+ <c>ERL_TIMEOUT</c>, and <c>ERL_ERROR</c>. The functions
+ <c>ei_rpc_to()</c> and <c>ei_xrpc_to()</c> returns 0 if
+ successful, otherwise -1. When failing, all four functions set
+ <c>erl_errno</c> to one of the following:</p>
<taglist>
<tag><c>EIO</c></tag>
<item>I/O error.</item>
diff --git a/lib/erl_interface/doc/src/erl_call_cmd.xml b/lib/erl_interface/doc/src/erl_call_cmd.xml
index 04b5ec74bf..19b159d7a3 100644
--- a/lib/erl_interface/doc/src/erl_call_cmd.xml
+++ b/lib/erl_interface/doc/src/erl_call_cmd.xml
@@ -116,6 +116,41 @@
expressions and returns the result from the last expression.
Returns <c>{ok,Result}</c> on success.</p>
</item>
+ <tag><c>-fetch_stdout</c></tag>
+ <item>
+ <p>
+ (<em>Optional.</em>) Executes the code, specified with
+ the <c>-a</c> or <c>-e</c> option, in a new process that
+ has a <seemfa marker="erts:erlang#group_leader/0">group
+ leader</seemfa> that forwards all stdout (standard
+ output) data so that it is printed to stdout of the
+ <c>erl_call</c> process. This means that stdout data
+ that are written during the execution of the called code,
+ by the code and by descendant processes, will be
+ forwarded (given that the group leader has not been
+ changed by a call to <seemfa
+ marker="erts:erlang#group_leader/2"><c>erlang:group_leader/2</c></seemfa>).
+ </p>
+ <p>
+ The printed data is UTF-8 encoded.
+ </p>
+ <p>
+ This option is only relevant together with the option
+ <c>-a</c> or <c>-e</c>.
+ </p>
+ <p>
+ See the documentation of <seeguide
+ marker="stdlib:io_protocol">the I/O protocol</seeguide>,
+ for more information about the group leader concept.
+ </p>
+ <note>
+ <p>
+ This option only works when <c>erl_call</c> is
+ interacting with a node with a version greater or equal
+ to OTP-24.
+ </p>
+ </note>
+ </item>
<tag><c>-h HiddenName</c></tag>
<item>
<p>(<em>Optional.</em>) Specifies the name of the hidden node
@@ -145,6 +180,12 @@
<c>-s</c> is specified, an Erlang node will (if
necessary) be started with <c>erl -name</c>.</p>
</item>
+ <tag><c>-no_result_term</c></tag>
+ <item>
+ <p>(<em>Optional.</em>) Do not print the result term. This
+ option is only relevant together with the options
+ <c>-a</c> and <c>-e</c>.</p>
+ </item>
<tag><c>-q</c></tag>
<item>
<p>(<em>Optional.</em>) Halts the Erlang node specified
@@ -292,6 +333,16 @@ start() ->
{registered_name,user}},
{<madonna@chivas.du.etx.ericsson.se,38,0>,
[]}]
+ ]]></code>
+ <p>To forward standard output without printing the result term
+ (<em>again, the input ends with EOF (Control-D)</em>):</p>
+ <code type="none"><![CDATA[
+erl_call -s -e -sname madonna -fetch_stdout -no_result_term
+io:format("Number of schedulers: ~p~n", [erlang:system_info(schedulers)]),
+io:format("Number of logical cores: ~p~n", [erlang:system_info(logical_processors_available)]).
+^D
+Number of schedulers: 8
+Number of logical cores: 8
]]></code>
</section>
</comref>
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index f1b6112960..f171bf1a8d 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -122,7 +122,8 @@ typedef LONG_PTR ssize_t; /* Sigh... */
#define ERL_DEMONITOR_P 20
#define ERL_MONITOR_P_EXIT 21
-
+/* For ei_xrpc_to */
+#define EI_RPC_FETCH_STDOUT 1
/* -------------------------------------------------------------------- */
/* Defines used for ei_get_type_internal() output */
/* -------------------------------------------------------------------- */
@@ -435,6 +436,8 @@ int ei_reg_send_tmo(ei_cnode* ec, int fd, char *server_name, char* buf, int len,
int ei_rpc(ei_cnode* ec, int fd, char *mod, char *fun,
const char* inbuf, int inbuflen, ei_x_buff* x);
+int ei_xrpc_to(ei_cnode* ec, int fd, char *mod, char *fun,
+ const char* buf, int len, int flags);
int ei_rpc_to(ei_cnode* ec, int fd, char *mod, char *fun,
const char* buf, int len);
int ei_rpc_from(ei_cnode* ec, int fd, int timeout, erlang_msg* msg,
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index b205eaca35..ac2255b3d4 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -1851,18 +1851,26 @@ int ei_xreceive_msg_tmo(int fd, erlang_msg *msg, ei_x_buff *x, unsigned ms)
return ei_do_receive_msg(fd, 0, msg, x, ms);
}
-/*
-* The RPC consists of two parts, send and receive.
-* Here is the send part !
-* { PidFrom, { call, Mod, Fun, Args, user }}
-*/
/*
-* Now returns non-negative number for success, negative for failure.
+* A remote process call consists of two parts, sending a request and
+* receiving a response. This function sends the request and the
+* ei_rpc_from function receives the response.
+*
+* Here is the term that is sent when (flags & EI_RPC_FETCH_STDOUT) != 0:
+*
+* { PidFrom, { call, Mod, Fun, Args, send_stdout_to_caller }}
+*
+* Here is the term that is sent otherwise:
+*
+* { PidFrom, { call, Mod, Fun, Args, user }}
+*
+* Returns a non-negative number for success and a negative number for
+* failure.
+*
*/
-int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
- const char *buf, int len)
+int ei_xrpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
+ const char *buf, int len, int flags)
{
-
ei_x_buff x;
erlang_pid *self = ei_self(ec);
int err = ERL_ERROR;
@@ -1872,10 +1880,10 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
goto einval;
if (ei_x_encode_tuple_header(&x, 2) < 0) /* A */
goto einval;
-
+
if (ei_x_encode_pid(&x, self) < 0) /* A 1 */
goto einval;
-
+
if (ei_x_encode_tuple_header(&x, 5) < 0) /* B A 2 */
goto einval;
if (ei_x_encode_atom(&x, "call") < 0) /* B 1 */
@@ -1886,14 +1894,19 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
goto einval;
if (ei_x_append_buf(&x, buf, len) < 0) /* B 4 */
goto einval;
- if (ei_x_encode_atom(&x, "user") < 0) /* B 5 */
- goto einval;
-
+ if (flags & EI_RPC_FETCH_STDOUT) {
+ if (ei_x_encode_atom(&x, "send_stdout_to_caller") < 0) /* B 5 */
+ goto einval;
+ } else {
+ if (ei_x_encode_atom(&x, "user") < 0) /* B 5 */
+ goto einval;
+ }
+
err = ei_send_reg_encoded(fd, self, "rex", x.buff, x.index);
if (err)
goto error;
-
- ei_x_free(&x);
+
+ ei_x_free(&x);
return 0;
@@ -1904,6 +1917,13 @@ error:
if (x.buff != NULL)
ei_x_free(&x);
return err;
+} /* xrpc_to */
+
+
+int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
+ const char *buf, int len)
+{
+ return ei_xrpc_to(ec, fd, mod, fun, buf, len, 0);
} /* rpc_to */
/*
diff --git a/lib/erl_interface/src/prog/erl_call.c b/lib/erl_interface/src/prog/erl_call.c
index 1c9bd69a96..dbcb722b12 100644
--- a/lib/erl_interface/src/prog/erl_call.c
+++ b/lib/erl_interface/src/prog/erl_call.c
@@ -84,6 +84,8 @@ struct call_flags {
int debugp;
int verbosep;
int haltp;
+ int fetch_stdout;
+ int print_result_term;
long port;
char *hostname;
char *cookie;
@@ -105,6 +107,9 @@ static void* ei_chk_malloc(size_t size);
static void* ei_chk_calloc(size_t nmemb, size_t size);
static void* ei_chk_realloc(void *old, size_t size);
static char* ei_chk_strdup(char *s);
+static int rpc_print_node_stdout(ei_cnode* ec, int fd, char *mod,
+ char *fun, const char* inbuf,
+ int inbuflen, ei_x_buff* x);
/* Converts the given hostname to a shortname, if required. */
static void format_node_hostname(const struct call_flags *flags,
@@ -146,6 +151,8 @@ int main(int argc, char *argv[])
ei_cnode ec;
flags.port = -1;
flags.hostname = NULL;
+ flags.fetch_stdout = 0;
+ flags.print_result_term = 1;
ei_init();
@@ -208,6 +215,10 @@ int main(int argc, char *argv[])
start_timeout(timeout);
i++;
+ } else if (strcmp(argv[i], "-fetch_stdout") == 0) {
+ flags.fetch_stdout = 1;
+ } else if (strcmp(argv[i], "-no_result_term") == 0) {
+ flags.print_result_term = 0;
} else if (strcmp(argv[i], "-__uh_test__") == 0) {
/* Fakes a failure in the call to ei_gethostbyname(h_hostname) so
* we can test the localhost fallback. */
@@ -596,7 +607,8 @@ int main(int argc, char *argv[])
len = read_stdin(&evalbuf);
{
int i = 0;
- char *p;
+ int rpc_res;
+ char *p;
ei_x_buff reply;
ei_encode_list_header(NULL, &i, 1);
@@ -611,10 +623,15 @@ int main(int argc, char *argv[])
ei_encode_empty_list(p, &i);
ei_x_new_with_version(&reply);
-
/* erl_format("[~w]", erl_mk_binary(evalbuf,len))) */
- if (ei_rpc(&ec, fd, "erl_eval", "eval_str", p, i, &reply) < 0) {
+ if (flags.fetch_stdout) {
+ rpc_res = rpc_print_node_stdout(&ec, fd, "erl_eval", "eval_str", p, i, &reply);
+ } else {
+ rpc_res = ei_rpc(&ec, fd, "erl_eval", "eval_str", p, i, &reply);
+ }
+
+ if (rpc_res < 0) {
fprintf(stderr,"erl_call: evaluating input failed: %s\n",
evalbuf);
free(p);
@@ -622,8 +639,10 @@ int main(int argc, char *argv[])
ei_x_free(&reply);
exit(1);
}
- i = 0;
- ei_print_term(stdout,reply.buff,&i);
+ if (flags.print_result_term) {
+ i = 0;
+ ei_print_term(stdout,reply.buff,&i);
+ }
free(p);
free(evalbuf); /* Allocated in read_stdin() */
ei_x_free(&reply);
@@ -635,7 +654,7 @@ int main(int argc, char *argv[])
if (flags.apply != NULL) {
char *mod,*fun,*args;
ei_x_buff e, reply;
-
+ int rpc_res;
split_apply_string(flags.apply, &mod, &fun, &args);
if (flags.verbosep) {
fprintf(stderr,"erl_call: module = %s, function = %s, args = %s\n",
@@ -651,14 +670,21 @@ int main(int argc, char *argv[])
ei_x_new_with_version(&reply);
- if (ei_rpc(&ec, fd, mod, fun, e.buff, e.index, &reply) < 0) {
+ if (flags.fetch_stdout) {
+ rpc_res = rpc_print_node_stdout(&ec, fd, mod, fun, e.buff, e.index, &reply);
+ } else {
+ rpc_res = ei_rpc(&ec, fd, mod, fun, e.buff, e.index, &reply);
+ }
+ if (rpc_res < 0) {
/* FIXME no error message and why -1 ? */
ei_x_free(&e);
ei_x_free(&reply);
exit(-1);
} else {
- int i = 0;
- ei_print_term(stdout,reply.buff,&i);
+ if (flags.print_result_term) {
+ int i = 0;
+ ei_print_term(stdout,reply.buff,&i);
+ }
ei_x_free(&e);
ei_x_free(&reply);
}
@@ -908,6 +934,13 @@ static void usage_noexit(const char *progname) {
fprintf(stderr," -d direct Erlang output to ~/.erl_call.out.<Nodename>\n");
fprintf(stderr," -e evaluate contents of standard input (e.g., echo \"X=1,Y=2,{X,Y}.\"|%s -e ...)\n",
progname);
+ fprintf(stderr,
+ " -fetch_stdout\n"
+ " execute the code, specified with the -a or -e option, in a new\n"
+ " process that has a group leader that forwards all stdout (standard\n"
+ " output) data so that it is printed to stdout of the\n"
+ " %s process. See the %s man page for additional information.\n",
+ progname, progname);
fprintf(stderr," -h specify a name for the erl_call client node\n");
fprintf(stderr," -m read and compile Erlang module from stdin\n");
fprintf(stderr," -n name of Erlang node, same as -name\n");
@@ -918,6 +951,7 @@ static void usage_noexit(const char *progname) {
" (e.g., %s -address my_host:36303 ...)\n"
" (cannot be combinated with -s, -n, -name and -sname)\n",
progname);
+ fprintf(stderr," -no_result_term do not print the result term\n");
fprintf(stderr," -timeout command timeout, in seconds\n");
fprintf(stderr," -q halt the Erlang node (overrides the -s switch)\n");
fprintf(stderr," -r use a random name for the erl_call client node\n");
@@ -989,3 +1023,87 @@ static char* ei_chk_strdup(char *s)
}
return p;
}
+
+/*
+ * Helper function that that:
+ *
+ * 1. Executes a function on a remote node
+ *
+ * 2. Forwards what the executed function and its subprocesses prints
+ * to stdout of this process
+ *
+ * 3. Returns the result of the executed function (the result term is
+ * written to the buffer pointed to by x)
+ *
+ * This function is similar to (and is based on) the function ei_rpc
+ */
+static int rpc_print_node_stdout(ei_cnode* ec, int fd, char *mod,
+ char *fun, const char* inbuf,
+ int inbuflen, ei_x_buff* x)
+{
+ int i, index;
+ int got_rex_response = 0;
+ int initial_buff_index = x->index;
+ ei_term t;
+ erlang_msg msg;
+ char rex[MAXATOMLEN];
+
+ if (ei_xrpc_to(ec, fd, mod, fun, inbuf, inbuflen, EI_RPC_FETCH_STDOUT) < 0) {
+ return ERL_ERROR;
+ }
+
+ while (!got_rex_response) {
+ /* ei_rpc_from() responds with a tick if it gets one... */
+ while ((i = ei_rpc_from(ec, fd, ERL_NO_TIMEOUT, &msg, x)) == ERL_TICK)
+ ;
+
+ if (i == ERL_ERROR) return i;
+
+ index = 0;
+ if (ei_decode_version(x->buff, &index, &i) < 0)
+ goto ebadmsg;
+
+ if (ei_decode_ei_term(x->buff, &index, &t) < 0)
+ goto ebadmsg;
+
+ if (t.ei_type != ERL_SMALL_TUPLE_EXT && t.ei_type != ERL_LARGE_TUPLE_EXT)
+ goto ebadmsg;
+
+ if (t.arity != 2)
+ goto ebadmsg;
+
+ if (ei_decode_atom(x->buff, &index, rex) < 0)
+ goto ebadmsg;
+
+ if (strcmp("rex_stdout", rex) == 0) {
+ int type;
+ int size;
+ char* binary_buff;
+ long actual_size;
+ ei_get_type(x->buff, &index, &type, &size);
+ if(type != ERL_BINARY_EXT) {
+ goto ebadmsg;
+ }
+ binary_buff = ei_chk_malloc(size + 1);
+ ei_decode_binary(x->buff, &index, binary_buff, &actual_size);
+ binary_buff[size] = '\0';
+ printf("%s", binary_buff);
+ free(binary_buff);
+ /* Reset the buffer as we need to read more and we have no
+ use for what we have already read */
+ x->index = initial_buff_index;
+ } else {
+ if(strcmp("rex", rex) != 0)
+ goto ebadmsg;
+ got_rex_response = 1;
+ }
+ }
+ /* remove header */
+ x->index -= index;
+ memmove(x->buff, &x->buff[index], x->index);
+ return 0;
+
+ebadmsg:
+
+ return ERL_ERROR;
+}
diff --git a/lib/erl_interface/test/erl_call_SUITE.erl b/lib/erl_interface/test/erl_call_SUITE.erl
index 9cfc2ac25c..8f1d0c9f90 100644
--- a/lib/erl_interface/test/erl_call_SUITE.erl
+++ b/lib/erl_interface/test/erl_call_SUITE.erl
@@ -27,14 +27,16 @@
random_cnode_name/1,
test_connect_to_host_port/1,
unresolvable_hostname/1,
- timeout/1]).
+ timeout/1,
+ test_fetch_stdout/1]).
all() ->
[smoke,
random_cnode_name,
test_connect_to_host_port,
unresolvable_hostname,
- timeout].
+ timeout,
+ test_fetch_stdout].
smoke(Config) when is_list(Config) ->
Name = atom_to_list(?MODULE)
@@ -129,6 +131,113 @@ test_connect_to_host_port_do(Name) ->
end,
ok.
+test_fetch_stdout(Config) when is_list(Config) ->
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ "fetch_stdout"
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+ try
+ test_fetch_stdout_do(Name)
+ after
+ halt_node(Name)
+ end,
+ ok.
+
+
+test_fetch_stdout_do(Name) ->
+ Port = start_node_and_get_port(Name),
+ %% Test that the -fetch_stdout option works
+ "hejok" = get_erl_call_result(["-address",
+ erlang:integer_to_list(Port),
+ "-a",
+ "io format [[104,101,106]]",
+ "-fetch_stdout"]),
+ %% Test that the -fetch_stdout option works together with
+ %% -no_result_term
+ "hej" = get_erl_call_result(["-address",
+ erlang:integer_to_list(Port),
+ "-a",
+ "io format [[104,101,106]]",
+ "-fetch_stdout",
+ "-no_result_term"]),
+ %% Test that we can print several times
+ MultiPrintCodeStr =
+ "io:format(\"hej\"),io:format(\"hej\"),io:format(\"hej\").\n",
+ "hejhejhej" = get_erl_call_result(["-address",
+ erlang:integer_to_list(Port),
+ "-a",
+ erlang_eval_call_string(MultiPrintCodeStr),
+ "-fetch_stdout",
+ "-no_result_term"]),
+ %% Test that we can print from a sub-process
+ SubProcPrintStr =
+ "begin\n"
+ " P = self(),\n"
+ " Printer = fun() ->\n"
+ " io:format(\"subhej\"),\n"
+ " receive\n"
+ " hej -> P ! hej\n"
+ " end\n"
+ " end,\n"
+ " PrinterPid = spawn(Printer),\n"
+ " PrinterPid ! hej,\n"
+ " receive\n"
+ " hej -> ok\n"
+ " end\n"
+ "end.\n",
+ "subhej" = get_erl_call_result(["-address",
+ erlang:integer_to_list(Port),
+ "-a",
+ erlang_eval_call_string(SubProcPrintStr),
+ "-fetch_stdout",
+ "-no_result_term"]),
+ %% Test that the remote group leader supports the multi-requests
+ %% request
+ TriggerMultiRequestsRequestStr =
+ "begin\n"
+ " %% Create multi request\n"
+ " MultiReqCollectGL =\n"
+ " spawn(\n"
+ " fun() ->\n"
+ " (fun GL(ReqList) ->\n"
+ " receive\n"
+ " {io_request, From, ReplyAs, Req} ->\n"
+ " From ! {io_reply, ReplyAs, ok},\n"
+ " GL([Req | ReqList]);\n"
+ " {get_reqs, Pid} ->\n"
+ " Pid ! {multi_req,\n"
+ " {requests, lists:reverse(ReqList)}}\n"
+ " end\n"
+ " end)([])\n"
+ " end),\n"
+ " OldGL = erlang:group_leader(),\n"
+ " erlang:group_leader(MultiReqCollectGL, self()),\n"
+ " io:format(\"test1\"),\n"
+ " io:format(\"test2\"),\n"
+ " io:format(\"test3\"),\n"
+ " MultiReqCollectGL ! {get_reqs, self()},\n"
+ " MultiReqsRequest =\n"
+ " receive\n"
+ " {multi_req, R} -> R\n"
+ " end,\n"
+ " erlang:group_leader(OldGL, self()),\n"
+ " %% Send multi request\n"
+ " erlang:group_leader() ! {io_request,self(), self(), MultiReqsRequest},\n"
+ " Me = self(),\n"
+ " receive {io_reply, Me, ok} -> ok end,\n"
+ " %% Send normal request\n"
+ " io:format(\"test4\")\n"
+ "end.\n",
+ "test1test2test3test4" =
+ get_erl_call_result(["-address",
+ erlang:integer_to_list(Port),
+ "-a",
+ erlang_eval_call_string(TriggerMultiRequestsRequestStr),
+ "-fetch_stdout",
+ "-no_result_term"]),
+ ok.
+
%% OTP-16604: Tests that erl_call works even when the local hostname cannot be
%% resolved.
unresolvable_hostname(_Config) ->
@@ -261,4 +370,6 @@ get_port_res(Port, Acc) when is_port(Port) ->
{Port, eof} ->
lists:flatten(Acc)
end.
-
+
+erlang_eval_call_string(CodeStr) ->
+ lists:flatten(io_lib:format("erl_eval eval_str [~w]",[CodeStr])).
diff --git a/lib/kernel/src/rpc.erl b/lib/kernel/src/rpc.erl
index 29b6d40592..6613028a0b 100644
--- a/lib/kernel/src/rpc.erl
+++ b/lib/kernel/src/rpc.erl
@@ -132,8 +132,22 @@ handle_call({call, Mod, Fun, Args, Gleader}, To, S) ->
%% Spawn not to block the rex server.
ExecCall = fun () ->
set_group_leader(Gleader),
+ GleaderBeforeCall = group_leader(),
Reply = execute_call(Mod, Fun, Args),
- gen_server:reply(To, Reply)
+ case Gleader of
+ {send_stdout_to_caller, _} ->
+ %% The group leader sends the response
+ %% to make sure that the client gets
+ %% all stdout that it should get before
+ %% the response
+ Ref = erlang:make_ref(),
+ GleaderBeforeCall ! {stop, self(), Ref, To, Reply},
+ receive
+ Ref -> ok
+ end;
+ _ ->
+ gen_server:reply(To, Reply)
+ end
end,
try
{_,Mon} = spawn_monitor(ExecCall),
@@ -199,9 +213,17 @@ handle_info({From, {send, Name, Msg}}, S) ->
ok %% It's up to Name to respond !!!!!
end,
{noreply, S};
-handle_info({From, {call, _Mod, _Fun, _Args, _Gleader} = Request}, S) ->
+handle_info({From, {call, Mod, Fun, Args, Gleader}}, S) ->
%% Special for hidden C node's, uugh ...
To = {From, ?NAME},
+ NewGleader =
+ case Gleader of
+ send_stdout_to_caller ->
+ {send_stdout_to_caller, From};
+ _ ->
+ Gleader
+ end,
+ Request = {call, Mod, Fun, Args, NewGleader},
case handle_call(Request, To, S) of
{noreply, _NewS} = Return ->
Return;
@@ -249,6 +271,8 @@ execute_call(Mod, Fun, Args) ->
set_group_leader(Gleader) when is_pid(Gleader) ->
group_leader(Gleader, self());
+set_group_leader({send_stdout_to_caller, CallerPid}) ->
+ group_leader(cnode_call_group_leader_start(CallerPid), self());
set_group_leader(user) ->
%% For example, hidden C nodes doesn't want any I/O.
Gleader = case whereis(user) of
@@ -1265,3 +1289,114 @@ pinfo(Pid, Item) when node(Pid) =:= node() ->
process_info(Pid, Item);
pinfo(Pid, Item) ->
block_call(node(Pid), erlang, process_info, [Pid, Item]).
+
+%% The following functions with the cnode_call_group_leader_ prefix
+%% are used for RPC requests with the group leader field set to
+%% send_stdout_to_caller. The group leader that these functions
+%% implement sends back data that are written to stdout during the
+%% call. The group leader implementation is heavily inspired by the
+%% example from the documentation of "The Erlang I/O Protocol".
+
+
+%% A record is used for the state even though it consists of only one
+%% pid to make future extension easier
+-record(cnode_call_group_leader_state,
+ {
+ caller_pid :: pid()
+ }).
+
+-spec cnode_call_group_leader_loop(State :: #cnode_call_group_leader_state{}) -> ok | no_return().
+
+cnode_call_group_leader_loop(State) ->
+ receive
+ {io_request, From, ReplyAs, Request} ->
+ {_, Reply, NewState}
+ = cnode_call_group_leader_request(Request, State),
+ From ! {io_reply, ReplyAs, Reply},
+ cnode_call_group_leader_loop(NewState);
+ {stop, StopRequesterPid, Ref, To, Reply} ->
+ gen_server:reply(To, Reply),
+ StopRequesterPid ! Ref,
+ ok;
+ _Unknown ->
+ cnode_call_group_leader_loop(State)
+ end.
+
+-spec cnode_call_group_leader_request(Request, State) -> Result when
+ Request :: any(),
+ State :: #cnode_call_group_leader_state{},
+ Result :: {ok | error, Reply, NewState},
+ Reply :: term(),
+ NewState :: #cnode_call_group_leader_state{}.
+
+cnode_call_group_leader_request({put_chars, Encoding, Chars},
+ State) ->
+ cnode_call_group_leader_put_chars(Chars, Encoding, State);
+cnode_call_group_leader_request({put_chars, Encoding, Module, Function, Args},
+ State) ->
+ try
+ cnode_call_group_leader_request({put_chars,
+ Encoding,
+ apply(Module, Function, Args)},
+ State)
+ catch
+ _:_ ->
+ {error, {error, Function}, State}
+ end;
+cnode_call_group_leader_request({requests, Reqs}, State) ->
+ cnode_call_group_leader_multi_request(Reqs, {ok, ok, State});
+cnode_call_group_leader_request({get_until, _, _, _, _, _}, State) ->
+ {error, {error,enotsup}, State};
+cnode_call_group_leader_request({get_chars, _, _, _}, State) ->
+ {error, {error,enotsup}, State};
+cnode_call_group_leader_request({get_line, _, _}, State) ->
+ {error, {error,enotsup}, State};
+cnode_call_group_leader_request({get_geometry,_}, State) ->
+ {error, {error,enotsup}, State};
+cnode_call_group_leader_request({setopts, _Opts}, State) ->
+ {error, {error,enotsup}, State};
+cnode_call_group_leader_request(getopts, State) ->
+ {error, {error,enotsup}, State};
+cnode_call_group_leader_request(_Other, State) ->
+ {error, {error,request}, State}.
+
+-spec cnode_call_group_leader_multi_request(Requests, PrevResponse) -> Result when
+ Requests :: list(),
+ PrevResponse :: {ok | error, Reply, State :: #cnode_call_group_leader_state{}},
+ Result :: {ok | error, Reply, NewState :: #cnode_call_group_leader_state{}},
+ Reply :: term().
+
+cnode_call_group_leader_multi_request([R|Rs], {ok, _Res, State}) ->
+ cnode_call_group_leader_multi_request(Rs, cnode_call_group_leader_request(R, State));
+cnode_call_group_leader_multi_request([_|_], Error) ->
+ Error;
+cnode_call_group_leader_multi_request([], Result) ->
+ Result.
+
+-spec cnode_call_group_leader_put_chars(Chars, Encoding, State) -> Result when
+ Chars :: unicode:latin1_chardata() | unicode:chardata() | unicode:external_chardata(),
+ Encoding :: unicode:encoding(),
+ State :: #cnode_call_group_leader_state{},
+ Result :: {ok | error, term(), NewState},
+ NewState :: #cnode_call_group_leader_state{}.
+
+cnode_call_group_leader_put_chars(Chars, Encoding, State) ->
+ CNodePid = State#cnode_call_group_leader_state.caller_pid,
+ case unicode:characters_to_binary(Chars,Encoding,utf8) of
+ Data when is_binary(Data) ->
+ CNodePid ! {rex_stdout, Data},
+ {ok, ok, State};
+ Error ->
+ {error, {error, Error}, state}
+ end.
+
+-spec cnode_call_group_leader_init(CallerPid :: pid()) -> ok | no_return().
+
+cnode_call_group_leader_init(CallerPid) ->
+ State = #cnode_call_group_leader_state{caller_pid = CallerPid},
+ cnode_call_group_leader_loop(State).
+
+-spec cnode_call_group_leader_start(CallerPid :: pid()) -> pid().
+
+cnode_call_group_leader_start(CallerPid) ->
+ spawn_link(fun() -> cnode_call_group_leader_init(CallerPid) end).