summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjell Winblad <kjellwinblad@gmail.com>2021-04-06 11:01:44 +0200
committerKjell Winblad <kjellwinblad@gmail.com>2021-04-06 11:01:44 +0200
commitc41c3553d6978d1d2e909f826c0f33499e845265 (patch)
tree7a3af2085c95e9d5d91b3afa3b9d30744f1a3858
parent41816872b72292e4650aa86b812ad448b3578f9a (diff)
parent4bfb707e60c2b2b34b2116102c4f431a09313e9e (diff)
downloaderlang-c41c3553d6978d1d2e909f826c0f33499e845265.tar.gz
Merge branch 'kjell/erl_call/output_to_stdout/OTP-17132'
* kjell/erl_call/output_to_stdout/OTP-17132: erl_call: Add -fetch_stdout and -no_result_term options
-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 d1b1dea892..fede6606e2 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).