diff options
author | Kjell Winblad <kjellwinblad@gmail.com> | 2021-04-06 11:01:44 +0200 |
---|---|---|
committer | Kjell Winblad <kjellwinblad@gmail.com> | 2021-04-06 11:01:44 +0200 |
commit | c41c3553d6978d1d2e909f826c0f33499e845265 (patch) | |
tree | 7a3af2085c95e9d5d91b3afa3b9d30744f1a3858 | |
parent | 41816872b72292e4650aa86b812ad448b3578f9a (diff) | |
parent | 4bfb707e60c2b2b34b2116102c4f431a09313e9e (diff) | |
download | erlang-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.xml | 109 | ||||
-rw-r--r-- | lib/erl_interface/doc/src/erl_call_cmd.xml | 51 | ||||
-rw-r--r-- | lib/erl_interface/include/ei.h | 5 | ||||
-rw-r--r-- | lib/erl_interface/src/connect/ei_connect.c | 52 | ||||
-rw-r--r-- | lib/erl_interface/src/prog/erl_call.c | 136 | ||||
-rw-r--r-- | lib/erl_interface/test/erl_call_SUITE.erl | 117 | ||||
-rw-r--r-- | lib/kernel/src/rpc.erl | 139 |
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). |