summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--erts/doc/src/erlang.xml7
-rw-r--r--erts/doc/src/notes.xml44
-rw-r--r--erts/emulator/beam/erl_bif_persistent.c8
-rw-r--r--erts/emulator/test/persistent_term_SUITE.erl40
-rw-r--r--erts/etc/unix/cerl.src20
-rw-r--r--erts/etc/unix/etp.py652
-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/Makefile.in3
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c52
-rw-r--r--lib/erl_interface/src/prog/erl_call.c889
-rw-r--r--lib/erl_interface/src/prog/erl_start.c634
-rw-r--r--lib/erl_interface/src/prog/erl_start.h47
-rw-r--r--lib/erl_interface/test/erl_call_SUITE.erl117
-rw-r--r--lib/eunit/src/eunit_surefire.erl14
-rw-r--r--lib/eunit/test/eunit_SUITE.erl9
-rw-r--r--lib/kernel/doc/src/notes.xml16
-rw-r--r--lib/kernel/src/rpc.erl139
-rw-r--r--lib/ssh/doc/src/notes.xml17
-rw-r--r--lib/ssl/src/ssl_certificate.erl10
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl75
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl2
-rw-r--r--lib/tools/doc/src/notes.xml16
-rw-r--r--otp_versions.table1
25 files changed, 2189 insertions, 788 deletions
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml
index 2bba258c07..384b0f21e0 100644
--- a/erts/doc/src/erlang.xml
+++ b/erts/doc/src/erlang.xml
@@ -11608,13 +11608,6 @@ improper_end</pre>
<p>When <c>PidPort</c> gets unlinked from a process <c>Pid2</c>.</p>
</item>
<tag>
- <marker id="trace_3_trace_messages_exit"></marker>
- <c>{trace, Pid, exit, Reason}</c>
- </tag>
- <item>
- <p>When <c>Pid</c> exits with reason <c>Reason</c>.</p>
- </item>
- <tag>
<marker id="trace_3_trace_messages_open"></marker>
<c>{trace, Port, open, Pid, Driver}</c>
</tag>
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index 497bb6737c..de11e50ec2 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -1369,6 +1369,50 @@
</section>
+<section><title>Erts 10.7.2.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed a bug in the timer implementation which could cause
+ timers that were set to more than 37.25 hours in the
+ future to be delayed. This could occur if there were
+ multiple timers scheduled to be triggered very close in
+ time, but still at different times, and the scheduler
+ thread handling the timers was not able to handle them
+ quickly enough. Delayed timers were in this case
+ triggered when another unrelated timer was triggered.</p>
+ <p>
+ Own Id: OTP-17253</p>
+ </item>
+ <item>
+ <p>
+ Fix bug in call_time tracing (used by eprof) that could
+ cause VM crash. Bug exists since OTP-22.2 (but not in
+ OTP-23).</p>
+ <p>
+ Own Id: OTP-17290 Aux Id: GH-4635 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fix a file descriptor leak when using sendfile and the
+ remote side closes the connection. This bug has been
+ present since OTP-21.0.</p>
+ <p>
+ Own Id: OTP-17244</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.7.2.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/erts/emulator/beam/erl_bif_persistent.c b/erts/emulator/beam/erl_bif_persistent.c
index b925c1e339..e45708e077 100644
--- a/erts/emulator/beam/erl_bif_persistent.c
+++ b/erts/emulator/beam/erl_bif_persistent.c
@@ -439,7 +439,7 @@ BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
BIF_RETTYPE persistent_term_get_0(BIF_ALIST_0)
{
- HashTable* hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
+ HashTable* hash_table;
TrapData* trap_data;
Eterm res = NIL;
Eterm magic_ref;
@@ -450,6 +450,8 @@ BIF_RETTYPE persistent_term_get_0(BIF_ALIST_0)
ERTS_BIF_YIELD0(BIF_TRAP_EXPORT(BIF_persistent_term_get_0), BIF_P);
}
+ hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
+
magic_ref = alloc_trap_data(BIF_P);
mbp = erts_magic_ref2bin(magic_ref);
trap_data = ERTS_MAGIC_BIN_DATA(mbp);
@@ -673,7 +675,7 @@ BIF_RETTYPE erts_internal_erase_persistent_terms_0(BIF_ALIST_0)
BIF_RETTYPE persistent_term_info_0(BIF_ALIST_0)
{
- HashTable* hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
+ HashTable* hash_table;
TrapData* trap_data;
Eterm res = NIL;
Eterm magic_ref;
@@ -684,6 +686,8 @@ BIF_RETTYPE persistent_term_info_0(BIF_ALIST_0)
ERTS_BIF_YIELD0(BIF_TRAP_EXPORT(BIF_persistent_term_info_0), BIF_P);
}
+ hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
+
magic_ref = alloc_trap_data(BIF_P);
mbp = erts_magic_ref2bin(magic_ref);
trap_data = ERTS_MAGIC_BIN_DATA(mbp);
diff --git a/erts/emulator/test/persistent_term_SUITE.erl b/erts/emulator/test/persistent_term_SUITE.erl
index e9ace3cd99..4ab84117c7 100644
--- a/erts/emulator/test/persistent_term_SUITE.erl
+++ b/erts/emulator/test/persistent_term_SUITE.erl
@@ -22,8 +22,10 @@
-include_lib("common_test/include/ct.hrl").
-export([all/0,suite/0,init_per_suite/1,end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2,
basic/1,purging/1,sharing/1,get_trapping/1,
destruction/1,
+ get_all_race/1,
info/1,info_trapping/1,killed_while_trapping/1,
off_heap_values/1,keys/1,collisions/1,
init_restart/1, put_erase_trapping/1,
@@ -45,6 +47,7 @@ suite() ->
all() ->
[basic,purging,sharing,get_trapping,info,info_trapping,
destruction,
+ get_all_race,
killed_while_trapping,off_heap_values,keys,collisions,
init_restart, put_erase_trapping, killed_while_trapping_put,
killed_while_trapping_erase,
@@ -62,6 +65,15 @@ end_per_suite(Config) ->
erts_debug:set_internal_state(available_internal_state, false),
Config.
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(_, _Config) ->
+ ok;
+end_per_testcase(get_all_race, _Config) ->
+ get_all_race_cleanup(),
+ ok.
+
basic(_Config) ->
Chk = chk(),
N = 777,
@@ -950,3 +962,31 @@ eval_bif_error(F, Args, Opts, T, Errors0) ->
do_error_info(T, Errors)
end
end.
+
+
+%% OTP-17298
+get_all_race(_Config) ->
+ N = 20 * erlang:system_info(schedulers_online),
+ persistent_term:put(get_all_race, N),
+ SPs = [spawn_link(fun() -> gar_setter(Seq) end) || Seq <- lists:seq(1, N)],
+ GPs = [spawn_link(fun gar_getter/0) || _ <- lists:seq(1, N)],
+ receive after 2000 -> ok end,
+ [begin unlink(Pid), exit(Pid,kill) end || Pid <- (SPs ++ GPs)],
+ ok.
+
+get_all_race_cleanup() ->
+ N = persistent_term:get(get_all_race, 0),
+ _ = persistent_term:erase(get_all_race),
+ [_ = persistent_term:erase(Seq) || Seq <- lists:seq(1, N)],
+ ok.
+
+gar_getter() ->
+ erts_debug:set_internal_state(reds_left, 1),
+ _ = persistent_term:get(),
+ gar_getter().
+
+gar_setter(Key) ->
+ erts_debug:set_internal_state(reds_left, 1),
+ persistent_term:erase(Key),
+ persistent_term:put(Key, {complex, term}),
+ gar_setter(Key).
diff --git a/erts/etc/unix/cerl.src b/erts/etc/unix/cerl.src
index f617736e43..adc20f5ac6 100644
--- a/erts/etc/unix/cerl.src
+++ b/erts/etc/unix/cerl.src
@@ -34,6 +34,8 @@
# You have to start beam in gdb using "run".
# -rgdb Run the debug compiled emulator in gdb.
# You have to start beam in gdb using "run".
+# -lldb Run the debug compiled emulator in lldb.
+# You have to start beam in lldb using "run".
# -dump Dump the bt of all threads in a core.
# -break F Run the debug compiled emulator in emacs and gdb and set break.
# The session is started, i.e. "run" is already don for you.
@@ -170,6 +172,10 @@ while [ $# -gt 0 ]; do
shift
GDB=gdb
;;
+ "-lldb")
+ shift
+ GDB=lldb
+ ;;
"-break")
shift
GDB=gdb
@@ -421,6 +427,20 @@ elif [ "x$GDB" = "xgdb" ]; then
echo "source $ROOTDIR/erts/etc/unix/etp-commands" > $cmdfile
# Fire up gdb in emacs...
exec gdb $GDBBP -x $cmdfile $gdbcmd
+elif [ "x$GDB" = "xlldb" ]; then
+ case "x$core" in
+ x)
+ beam_args=`$EXEC -emu_args_exit ${1+"$@"}`
+ lldbcmd="-- $beam_args"
+ ;;
+ *)
+ lldbcmd="--core ${core}"
+ ;;
+ esac
+ cmdfile="/tmp/.cerllldb.$$"
+ echo "env TERM=dumb" > $cmdfile
+ echo "command script import $ROOTDIR/erts/etc/unix/etp.py" >> $cmdfile
+ exec lldb -s $cmdfile $EMU_NAME $lldbcmd
elif [ "x$GDB" = "xegdb" ]; then
if [ "x$EMACS" = "x" ]; then
EMACS=emacs
diff --git a/erts/etc/unix/etp.py b/erts/etc/unix/etp.py
new file mode 100644
index 0000000000..ca8a15d69d
--- /dev/null
+++ b/erts/etc/unix/etp.py
@@ -0,0 +1,652 @@
+# coding=utf-8
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2013-2022. 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%
+#
+#
+# This script was orinally written by Anthony Ramine (aka nox) in 2013
+# A lot of things have changed since then, but the same base remains.
+#
+
+import re
+import lldb
+import shlex
+
+unquoted_atom_re = re.compile(u'^[a-zß-öø-ÿ][a-zA-Zß-öø-ÿ0-9@]*$')
+
+def __lldb_init_module(debugger, internal_dict):
+ debugger.HandleCommand('type format add -f hex Eterm')
+ debugger.HandleCommand('type format add -f hex BeamInstr')
+ debugger.HandleCommand('type summary add -F etp.eterm_summary Eterm')
+ debugger.HandleCommand('command script add -f etp.processes_cmd etp-processes')
+ debugger.HandleCommand('command script add -f etp.process_info_cmd etp-process-info')
+ debugger.HandleCommand('command script add -f etp.stacktrace_cmd etp-stacktrace')
+ debugger.HandleCommand('command script add -f etp.stackdump_cmd etp-stackdump')
+ debugger.HandleCommand('command script add -f etp.eterm_cmd etp')
+
+####################################
+## Print all processes in the system
+####################################
+def processes_cmd(debugger, command, result, internal_dict):
+ target = debugger.GetSelectedTarget()
+ proc = erts_proc(target)
+ proc_r_o = proc.GetChildMemberWithName('r').GetChildMemberWithName('o')
+ proc_max_ix = proc_r_o.GetChildMemberWithName('max')
+ proc_tab = proc_r_o.GetChildMemberWithName('tab').Cast(ProcessPtrPtr(target))
+ proc_cnt = proc.GetChildMemberWithName('vola').GetChildMemberWithName('tile').GetChildMemberWithName('count').GetChildMemberWithName('counter').unsigned
+ invalid_proc = global_var('erts_invalid_process', target).address_of
+ for proc_ix in range(0, proc_max_ix.unsigned):
+ proc = offset(proc_ix, proc_tab).deref
+ if proc.unsigned != 0 and proc.unsigned != invalid_proc.unsigned:
+ print('---')
+ print(' Pix: %d' % proc_ix)
+ process_info(proc)
+ proc_cnt -= 1
+ if proc_cnt == 0:
+ break
+
+############################################
+## Print process-info about a single process
+############################################
+def process_info_cmd(debugger, command, result, internal_dict):
+ target = debugger.GetSelectedTarget()
+ proc = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(command).Cast(ProcessPtr(target))
+ process_info(proc)
+
+def process_info(proc):
+ print(' Pid: %s' % eterm(proc.GetChildMemberWithName('common').GetChildMemberWithName('id')))
+ print(' State: %s' % process_state(proc))
+ print(' Flags: %s' % process_flags(proc))
+ current = proc.GetChildMemberWithName('current')
+ if current.unsigned != 0 and proc.GetChildMemberWithName('state').GetChildMemberWithName('counter').unsigned & 0x800 == 0:
+ print(' Current function: %s' % mfa(current))
+ else:
+ print(' Current function: %s' % 'unknown')
+ i = proc.GetChildMemberWithName('i')
+ if i.unsigned != 0:
+ print(' I: %s' % eterm(i))
+ else:
+ print(' I: %s' % 'unknown')
+ print(' Pointer: %#x' % proc.unsigned)
+
+def process_state(proc):
+ state = proc.GetChildMemberWithName('state').unsigned
+ res = ''
+ if state & 0x80000000:
+ res += "GARBAGE<0x80000000> | "
+ if state & 0x40000000:
+ res += "dirty-running-sys | "
+ if state & 0x20000000:
+ res += "dirty-running | "
+ if state & 0x10000000:
+ res += "dirty-active-sys | "
+ if state & 0x8000000:
+ res += "dirty-io-proc | "
+ if state & 0x4000000:
+ res += "dirty-cpu-proc | "
+ if state & 0x2000000:
+ res += "sig-q | "
+ if state & 0x1000000:
+ res += "off-heap-msgq | "
+ if state & 0x800000:
+ res += "delayed-sys | "
+ if state & 0x400000:
+ res += "proxy | "
+ proxy_process = True
+ else:
+ proxy_process = False
+ if state & 0x200000:
+ res += "running-sys | "
+ if state & 0x100000:
+ res += "active-sys | "
+ if state & 0x80000:
+ res += "sig-in-q | "
+ if state & 0x40000:
+ res += "sys-tasks | "
+ if state & 0x20000:
+ res += "garbage-collecting | "
+ if state & 0x10000:
+ res += "suspended | "
+ if state & 0x8000:
+ res += "running | "
+ if state & 0x4000:
+ res += "in-run-queue | "
+ if state & 0x2000:
+ res += "active | "
+ if state & 0x1000:
+ res += "unused | "
+ if state & 0x800:
+ res += "exiting | "
+ if state & 0x400:
+ res += "free | "
+ if state & 0x200:
+ res += "in-prq-low | "
+ if state & 0x100:
+ res += "in-prq-normal | "
+ if state & 0x80:
+ res += "in-prq-high | "
+ if state & 0x40:
+ res += "in-prq-max | "
+ if state & 0x30 == 0x0:
+ res += "prq-prio-max | "
+ elif state & 0x30 == 0x10:
+ res += "prq-prio-high | "
+ elif state & 0x30 == 0x20:
+ res += "prq-prio-normal | "
+ else:
+ res += "prq-prio-low | "
+ if state & 0xc == 0x0:
+ res += "usr-prio-max | "
+ elif state & 0xc == 0x4:
+ res += "usr-prio-high | "
+ elif state & 0xc == 0x8:
+ res += "usr-prio-normal | "
+ else:
+ res += "usr-prio-low | "
+ if state & 0x3 == 0x0:
+ res += "act-prio-max"
+ elif state & 0x3 == 0x1:
+ res += "act-prio-high"
+ elif state & 0x3 == 0x2:
+ res += "act-prio-normal"
+ else:
+ res += "act-prio-low"
+ return res
+
+def process_flags(proc):
+ flags = proc.GetChildMemberWithName('flags').unsigned
+ res = ''
+ if flags & ~((1 << 24)-1):
+ res += "GARBAGE<%#x> " % (flags & ~((1 << 24)-1))
+ if flags & (1 << 22):
+ res += "trap-exit "
+ if flags & (1 << 21):
+ res += "hibernated "
+ if flags & (1 << 20):
+ res += "dirty-minor-gc "
+ if flags & (1 << 19):
+ res += "dirty-major-gc "
+ if flags & (1 << 18):
+ res += "dirty-gc-hibernate "
+ if flags & (1 << 17):
+ res += "dirty-cla "
+ if flags & (1 << 16):
+ res += "delayed-del-proc "
+ if flags & (1 << 15):
+ res += "have-blocked-nmsb "
+ if flags & (1 << 14):
+ res += "shdlr-onln-wait-q "
+ if flags & (1 << 13):
+ res += "delay-gc "
+ if flags & (1 << 12):
+ res += "abandoned-heap-use "
+ if flags & (1 << 11):
+ res += "disable-gc "
+ if flags & (1 << 10):
+ res += "force-gc "
+ if flags & (1 << 9):
+ res += "ets-super-user "
+ if flags & (1 << 8):
+ res += "have-blocked-msb "
+ if flags & (1 << 7):
+ res += "using-ddll "
+ if flags & (1 << 6):
+ res += "distribution "
+ if flags & (1 << 5):
+ res += "using-db "
+ if flags & (1 << 4):
+ res += "need-fullsweep "
+ if flags & (1 << 3):
+ res += "heap-grow "
+ if flags & (1 << 2):
+ res += "timo "
+ if flags & (1 << 1):
+ res += "inslpqueue "
+ if flags & (1 << 0):
+ res += "hibernate-sched "
+ return res
+
+############################################
+## Print the stacktrace of a single process
+############################################
+def stacktrace_cmd(debugger, command, result, internal_dict):
+ target = debugger.GetSelectedTarget()
+ proc = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(command).Cast(ProcessPtr(target))
+ stackdump(proc, False)
+
+############################################
+## Print the stackdump of a single process
+############################################
+def stackdump_cmd(debugger, command, result, internal_dict):
+ target = debugger.GetSelectedTarget()
+ proc = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(command).Cast(ProcessPtr(target))
+ stackdump(proc, True)
+
+def stackdump(proc, dump):
+ stop = proc.GetChildMemberWithName('stop')
+ send = proc.GetChildMemberWithName('hend')
+ cnt = 0
+ if proc.GetChildMemberWithName('state').GetChildMemberWithName('counter').unsigned & 0x8000:
+ print('%%%%%% WARNING: The process is currently running, so c_p->stop will not be correct')
+ print(F'%% Stacktrace ({send.unsigned - stop.unsigned})');
+ i = proc.GetChildMemberWithName('i')
+ if i.unsigned != 0:
+ print(F'I: {eterm(i)}')
+ while stop.unsigned < send.unsigned:
+ if stop.deref.unsigned & 0x3 == 0x0 or dump:
+ print(F'{cnt}: {eterm(stop.deref)}')
+ cnt += 1
+ stop = offset(1, stop)
+
+############################################
+## Print an eterm
+############################################
+def eterm_cmd(debugger, command, result, internal_dict):
+ args = shlex.split(command)
+ target = debugger.GetSelectedTarget()
+ term = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(args[0]).Cast(EtermPtr(target))
+ if len(args) >= 2:
+ print(eterm(term, int(args[1])))
+ else:
+ print(eterm(term))
+
+############################################
+## Print the summary of an Eterm
+############################################
+def eterm_summary(valobj, internal_dict):
+ if valobj.TypeIsPointerType():
+ return ''
+ return F'{valobj.unsigned:#x} {eterm(valobj, 5)}'
+
+def eterm(valobj, depth = float('inf')):
+ val = valobj.unsigned
+ tag = val & 0x3
+ if tag == 0x1:
+ return cons(valobj, depth)
+ elif tag == 0x2:
+ return boxed(valobj, depth)
+ elif tag == 0x3:
+ return imm(valobj)
+ elif val == 0x0:
+ return '<the non-value>'
+ elif val == 0x4:
+ return '<the non-value debug>'
+ else:
+ return cp(valobj)
+
+def cons(valobj, depth = float('inf')):
+ items = []
+ cdr = valobj
+ improper = False
+ truncated = False
+ depth *= 20
+
+ while True:
+ ptr = cdr.CreateValueFromData(
+ "unconsed",
+ lldb.SBData.CreateDataFromInt(cdr.unsigned - 1),
+ EtermPtr(cdr.target))
+ items.append((ptr.deref, depth // 20)); # Append depth, car
+ if ptr.deref.unsigned & 0xF == 0xF:
+ depth -= 1
+ else:
+ depth -= 20
+ cdr = offset(1,ptr).deref
+ if is_nil(cdr):
+ break
+ if cdr.unsigned & 0x1 == 0:
+ improper = True
+ break
+ if depth <= 1:
+ truncated = True
+ break
+
+ if improper:
+ return '#ImproperList'
+
+ ## Try to print as ascii first
+ chars = ''
+ isprintable = True
+ for car, car_depth in items:
+ if car.unsigned & 0xF == 0xF:
+ if car.unsigned >> 4 == 10:
+ chars += '\\n'
+ elif car.unsigned >> 4 == 9:
+ chars += '\\t'
+ else:
+ chars += f'{car.unsigned >> 4:c}'
+ else:
+ isprintable = False
+ break
+ isprintable = isprintable and chars.isprintable()
+ if isprintable:
+ if not truncated:
+ return F'"{chars}"'
+ else:
+ return F'"{chars}..."'
+
+ ## If not printable, we print the objects
+ objs = []
+ chars = '['
+ for car, car_depth in items:
+ objs.append(eterm(car, car_depth))
+ if not truncated:
+ return '[' + ','.join(objs) + ']'
+ else:
+ return '[' + ','.join(objs) + '|...]'
+
+def boxed(valobj, depth = float('inf')):
+ ptr = valobj.CreateValueFromData(
+ "unboxed",
+ lldb.SBData.CreateDataFromInt(valobj.unsigned - 2),
+ EtermPtr(valobj.target))
+ boxed_hdr = ptr.deref.unsigned
+ if boxed_hdr & 0x3f == 0x00:
+ arity = (boxed_hdr >> 6)
+ terms = []
+ for x in range(1, arity+1):
+ if depth <= 1:
+ terms.append('...')
+ break
+ depth -= 1
+ terms.append(eterm(offset(x, ptr).deref, depth))
+ res = ','.join(terms)
+ return F"{{{res}}}"
+ if boxed_hdr & 0x3c == 0x3c:
+ if boxed_hdr & 0xc0 == 0x0:
+ return "flat_map"
+ else:
+ return "hash_map"
+ boxed_type = (boxed_hdr >> 2) & 0xF
+ if boxed_type == 0xC:
+ return '#ExternalPid'
+ if boxed_type == 0xD:
+ return '#ExternalPort'
+ if boxed_type == 0x2 or boxed_type == 0x3:
+ return '#Bignum'
+ if boxed_type == 0x6:
+ return '#Float'
+ if boxed_type == 0x4:
+ return '#Ref'
+ if boxed_type == 0xE:
+ return '#ExternalRef'
+ if boxed_type == 0x5:
+ return '#Fun'
+ if boxed_type == 0x8:
+ return '#RefcBin'
+ if boxed_type == 0x9:
+ return '#HeapBin'
+ if boxed_type == 0xA:
+ return '#SubBin'
+ return F'#Boxed<{valobj.unsigned}>'
+
+def imm(valobj):
+ val = valobj.unsigned
+ if (val & 0x3) != 3:
+ return '#NotImmediate<%#x>' % val
+ tag = val & 0xF
+ if tag == 0x3:
+ return pid(valobj)
+ elif tag == 0x7:
+ return port(valobj)
+ elif tag == 0xF:
+ return str(val >> 4)
+ elif tag == 0xB:
+ # Immediate2
+ tag2 = val & 0x3F
+ if tag2 == 0x0B:
+ return atom(valobj)
+ elif tag2 == 0x1B:
+ return F'#Catch<{val>>6:#x}>'
+ elif is_nil(valobj):
+ return '[]'
+ return '#UnknownImmediate<%#x>' % val
+
+# Continuation pointers
+
+def cp(valobj):
+ mfaptr = erts_lookup_function_info(valobj)
+ if mfaptr == None:
+ return '#Cp<%#x>' % valobj.unsigned
+ else:
+ return '#Cp<%s>' % mfa(mfaptr)
+
+# Pids and ports
+
+def pid(valobj):
+ val = valobj.unsigned
+ if (val & 0xF) == 0x3:
+ target = valobj.target
+ if etp_arch_bits(target) == 64:
+ if etp_big_endian(target):
+ data = (val >> 35) & 0x0FFFFFFF
+ else:
+ data = (val >> 4) & 0x0FFFFFFF
+ else:
+ data = pixdata2data(valobj)
+ return '<0.%u.%u>' % (data & 0x7FFF, (data >> 15) & 0x1FFF)
+ else:
+ return '#NotPid<%#x>' % val
+
+def port(valobj):
+ val = valobj.unsigned
+ if (val & 0xF) == 0x7:
+ target = valobj.target
+ if etp_arch_bits(target) == 64 and not etp_halfword(target):
+ if etp_big_endian(target):
+ data = (val >> 36) & 0x0FFFFFFF
+ else:
+ data = (val >> 4) & 0x0FFFFFFF
+ else:
+ data = pixdata2data(valobj)
+ return '#Port<0.%u>' % data
+ else:
+ return '#NotPort<%#x>' % val
+
+# Strings and atoms
+
+def atom(valobj):
+ val = valobj.unsigned
+ if (val & 0x3F) == 0x0B:
+ name = atom_name(atom_tab(valobj))
+ if unquoted_atom_re.match(name):
+ return str(name)
+ else:
+ return quoted_name(name, "'")
+ else:
+ return '#NotAtom<%#x>' % val
+
+def atom_name(entry):
+ name = entry.GetChildMemberWithName('name')
+ length = entry.GetChildMemberWithName('len').unsigned
+ data = name.GetPointeeData(0, length).uint8s
+ return ''.join(map(chr, data))
+
+def quoted_name(name, quote):
+ return quote + ''.join(map(lambda c: quoted_char(c, quote), name)) + quote
+
+def quoted_char(c, quote):
+ point = ord(c)
+ if c == quote:
+ return '\\' + quote
+ elif point == 0x08:
+ return '\\b'
+ elif point == 0x09:
+ return '\\t'
+ elif point == 0x0A:
+ return '\\n'
+ elif point == 0x0B:
+ return '\\v'
+ elif point == 0x0C:
+ return '\\f'
+ elif point == 0x0D:
+ return '\\e'
+ elif point >= 0x20 and point <= 0x7E or point >= 0xA0:
+ return c
+ elif (point > 0xFF):
+ return '#NotChar<%#x>' % c
+ else:
+ return '\\%03o' % point
+
+# Constants
+
+MI_FUNCTIONS = 13
+MI_NUM_FUNCTIONS = 0
+
+def is_nil(value):
+ ## We handle both -5 and 0x3b as NIL values so that this script
+ ## works with more versions
+ return value.signed == -5 or value.unsigned == 0x3b
+
+# Types
+
+def Atom(target):
+ return target.FindFirstType('Atom')
+
+def Char(target):
+ return target.FindFirstType('char')
+def CharPtr(target):
+ return Char(target).GetPointerType()
+
+def Eterm(target):
+ return target.FindFirstType('Eterm')
+def EtermPtr(target):
+ return Eterm(target).GetPointerType()
+def EtermPtrPtr(target):
+ return EtermPtr(target).GetPointerType()
+
+def Range(target):
+ return target.FindFirstType('Range')
+
+def BeamInstr(target):
+ return target.FindFirstType('BeamInstr')
+def BeamInstrPtr(target):
+ return BeamInstr(target).GetPointerType()
+
+def ErtsCodeInfo(target):
+ return target.FindFirstType('ErtsCodeInfo')
+def ErtsCodeInfoPtr(target):
+ return ErtsCodeInfo(target).GetPointerType()
+
+def Process(target):
+ return target.FindFirstType('Process')
+def ProcessPtr(target):
+ return Process(target).GetPointerType()
+def ProcessPtrPtr(target):
+ return ProcessPtr(target).GetPointerType()
+
+# Globals
+
+def erts_atom_table(target):
+ return global_var('erts_atom_table', target)
+
+def erts_proc(target):
+ return global_var('erts_proc', target)
+
+def etp_arch_bits(target):
+ return global_var('etp_arch_bits', target).unsigned
+
+def etp_big_endian(target):
+ return global_var('etp_endianness', target).unsigned > 0
+
+def etp_halfword(target):
+ return False
+
+def the_active_code_index(target):
+ return global_var('the_active_code_index', target)
+
+# Functions
+
+def atom_tab(valobj):
+ idx = valobj.unsigned
+ target = valobj.target
+ seg = erts_atom_table(target).GetChildMemberWithName('seg_table')
+ slot = offset(idx >> 16, seg).deref
+ entry = offset(idx >> 6 & 0x3FF, slot).deref
+ return entry.Cast(Atom(target).GetPointerType())
+
+def erts_lookup_function_info(valobj):
+ r = find_range(valobj)
+ if r is None:
+ return None
+ pc = valobj.unsigned
+ target = valobj.target
+ start = r.GetChildMemberWithName('start').Cast(EtermPtr(target))
+ curr = offset(MI_FUNCTIONS, start).Cast(ErtsCodeInfoPtr(target).GetPointerType())
+ prev = curr
+ cnt = offset(MI_NUM_FUNCTIONS, start).deref.unsigned
+ for x in range(0, cnt):
+ prev = curr
+ curr = offset(1, curr)
+ if pc < curr.deref.unsigned:
+ return prev.deref.GetChildMemberWithName('mfa')
+ return None
+
+def find_range(valobj):
+ pc = valobj.unsigned
+ target = valobj.target
+ active = the_active_code_index(target).unsigned
+ ranges = offset(active, global_var('r', target))
+ n = ranges.GetChildMemberWithName('n').unsigned
+ low = ranges.GetChildMemberWithName('modules')
+ high = offset(n, low)
+ range_type = Range(target)
+ range_pointer_type = range_type.GetPointerType()
+ mid = ranges.GetChildMemberWithName('mid').Cast(range_pointer_type)
+ while low.unsigned < high.unsigned:
+ if pc < mid.GetChildMemberWithName('start').unsigned:
+ high = mid
+ elif pc > mid.GetChildMemberWithName('end').GetChildMemberWithName('counter').unsigned:
+ low = offset(1, mid).Cast(range_pointer_type)
+ else:
+ return mid
+ length = (high.unsigned - low.unsigned) // range_type.size
+ mid = offset(length // 2, low)
+ return None
+
+def mfa(mfa):
+ return '%s:%s/%d' % (eterm(mfa.GetChildMemberWithName('module')),
+ eterm(mfa.GetChildMemberWithName('function')),
+ mfa.GetChildMemberWithName('arity').unsigned)
+
+def pixdata2data(valobj):
+ pixdata = valobj.unsigned
+ proc = erts_proc(target)
+ ro = proc.GetChildMemberWithName('r').GetChildMemberWithName('o')
+ pix_mask = ro.GetChildMemberWithName('pix_mask').unsigned
+ pix_cl_mask = ro.GetChildMemberWithName('pix_cl_mask').unsigned
+ pix_cl_shift = ro.GetChildMemberWithName('pix_cl_shift').unsigned
+ pix_cli_mask = ro.GetChildMemberWithName('pix_cli_mask').unsigned
+ pix_cli_shift = ro.GetChildMemberWithName('pix_cli_shift').unsigned
+ data = pixdata & ~pix_mask
+ data |= (pixdata >> pix_cl_shift) & pix_cl_mask
+ data |= (pixdata & pix_cli_mask) << pix_cli_shift
+ return data
+
+# LLDB utils
+
+def global_var(name, target):
+ return target.FindGlobalVariables(name, 1)[0]
+
+def offset(i, valobj):
+ # print("offset(" + str(i) + ", " + str(valobj.unsigned) + ")")
+ val = valobj.GetChildAtIndex(i, lldb.eNoDynamicValues, True)
+ if valobj.TypeIsPointerType():
+ return val.address_of.Cast(valobj.GetType())
+ else:
+ return val
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/Makefile.in b/lib/erl_interface/src/Makefile.in
index 061fd05a1d..56fdaa608e 100644
--- a/lib/erl_interface/src/Makefile.in
+++ b/lib/erl_interface/src/Makefile.in
@@ -390,8 +390,7 @@ NEVERUSED = \
send_link.c
ERLCALL = \
- prog/erl_call.c \
- prog/erl_start.c
+ prog/erl_call.c
# Note that encode/decode_term.c defines ei functions that is
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..60940d7ae6 100644
--- a/lib/erl_interface/src/prog/erl_call.c
+++ b/lib/erl_interface/src/prog/erl_call.c
@@ -20,13 +20,21 @@
*/
/*
- * Function: Makes it possible to send and receive Erlang
- * messages from the (Unix) command line.
- * Note: We don't free any memory at all since we only
- * live for a short while.
+ * Description: This file implements the erl_call command line
+ * utility. The erl_call command can be used to:
+ *
+ * * Execute code on an Erlang node and get the result back
+ * * Start and stop Erlang nodes
+ * * Upload and compile a module on an Erlang node
+ *
+ * See the erl_call man page or HTML documentation for additional
+ * information.
*
*/
+/* An exception from using eidef.h, use config.h directly */
+#include "config.h"
+
#ifdef __WIN32__
#include <winsock2.h>
#include <direct.h>
@@ -35,16 +43,23 @@
#else /* unix */
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/times.h>
#include <sys/types.h>
#include <sys/uio.h>
-#include <sys/time.h>
+#include <sys/wait.h>
+#include <time.h>
#include <unistd.h>
-#include <sys/param.h>
-#include <netdb.h>
-#include <sys/times.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
@@ -59,6 +74,8 @@
#endif
+#include <sys/types.h>
+
#include <stdio.h>
#include <stdlib.h>
@@ -69,8 +86,24 @@
#include "ei.h"
#include "ei_resolve.h"
-#include "erl_start.h" /* FIXME remove dependency */
+#define ERL_START_MSG "gurka" /* make something up */
+#define ERL_START_TIME 10000 /* wait this long (ms) */
+#define ERL_START_LOGFILE ".erl_start.out" /* basename of logfile */
+
+/* flags used by erl_connect and erl_xconnect */
+#define ERL_START_ENODE 0x0001
+#define ERL_START_EPMD 0x0002
+#define ERL_START_LONG 0x0004
+#define ERL_START_COOKIE 0x0008
+#define ERL_START_DEBUG 0x0010
+#define ERL_START_VERBOSE 0x0020
+#define ERL_START_REMOTE 0x0040
+
+/* error return values */
+#define ERL_S_TIMEOUT -51 /* a timeout occurred */
+#define ERL_BADARG -52 /* an argument contained an incorrect value */
+#define ERL_SYS_ERROR -99 /* a system error occurred (check errno) */
struct call_flags {
int startp;
@@ -84,6 +117,8 @@ struct call_flags {
int debugp;
int verbosep;
int haltp;
+ int fetch_stdout;
+ int print_result_term;
long port;
char *hostname;
char *cookie;
@@ -93,6 +128,9 @@ struct call_flags {
char *script;
};
+/* start an erlang system */
+int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr addr, int flags,
+ char *erl, char *add_args[]);
static void usage_arg(const char *progname, const char *switchname);
static void usage_error(const char *progname, const char *switchname);
static void usage(const char *progname);
@@ -105,6 +143,10 @@ 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);
+static void exit_free_flags_fields(int exit_status, struct call_flags* flags);
/* Converts the given hostname to a shortname, if required. */
static void format_node_hostname(const struct call_flags *flags,
@@ -146,6 +188,13 @@ int main(int argc, char *argv[])
ei_cnode ec;
flags.port = -1;
flags.hostname = NULL;
+ flags.fetch_stdout = 0;
+ flags.print_result_term = 1;
+ flags.script = NULL;
+ flags.hidden = NULL;
+ flags.apply = NULL;
+ flags.cookie = NULL;
+ flags.node = NULL;
ei_init();
@@ -159,7 +208,9 @@ int main(int argc, char *argv[])
if (i+1 >= argc) {
usage_arg(progname, "-sname ");
}
-
+ if (flags.node != NULL) {
+ free(flags.node);
+ }
flags.node = ei_chk_strdup(argv[i+1]);
i++;
flags.use_long_name = 0;
@@ -167,7 +218,9 @@ int main(int argc, char *argv[])
if (i+1 >= argc) {
usage_arg(progname, "-name ");
}
-
+ if (flags.node != NULL) {
+ free(flags.node);
+ }
flags.node = ei_chk_strdup(argv[i+1]);
i++;
flags.use_long_name = 1;
@@ -180,16 +233,24 @@ int main(int argc, char *argv[])
char* address_string_end = strchr(hostname_port_arg, ':');
if (address_string_end == NULL) {
flags.port = strtol(hostname_port_arg, NULL, 10);
+ free(hostname_port_arg);
+ hostname_port_arg = NULL;
} else {
flags.port = strtol(address_string_end + 1, NULL, 10);
/* Remove port part from hostname_port_arg*/
*address_string_end = '\0';
if (strlen(hostname_port_arg) > 0) {
flags.hostname = hostname_port_arg;
+ } else {
+ free(hostname_port_arg);
+ hostname_port_arg = NULL;
}
}
if (flags.port < 1 || flags.port > 65535) {
+ if (hostname_port_arg != NULL) {
+ free(hostname_port_arg);
+ }
usage_error(progname, "-address");
}
i++;
@@ -208,6 +269,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. */
@@ -247,6 +312,9 @@ int main(int argc, char *argv[])
usage_arg(progname, "-c ");
}
flags.cookiep = 1;
+ if (flags.cookie != NULL) {
+ free(flags.cookie);
+ }
flags.cookie = ei_chk_strdup(argv[i+1]);
i++;
break;
@@ -254,6 +322,9 @@ int main(int argc, char *argv[])
if (i+1 >= argc) {
usage_arg(progname, "-n ");
}
+ if (flags.node != NULL) {
+ free(flags.node);
+ }
flags.node = ei_chk_strdup(argv[i+1]);
flags.use_long_name = 1;
i++;
@@ -262,6 +333,9 @@ int main(int argc, char *argv[])
if (i+1 >= argc) {
usage_arg(progname, "-h ");
}
+ if (flags.hidden != NULL) {
+ free(flags.hidden);
+ }
flags.hidden = ei_chk_strdup(argv[i+1]);
i++;
break;
@@ -269,6 +343,9 @@ int main(int argc, char *argv[])
if (i+1 >= argc) {
usage_arg(progname, "-x ");
}
+ if (flags.script != NULL) {
+ free(flags.script);
+ }
flags.script = ei_chk_strdup(argv[i+1]);
i++;
break;
@@ -276,6 +353,9 @@ int main(int argc, char *argv[])
if (i+1 >= argc) {
usage_arg(progname, "-a ");
}
+ if (flags.apply != NULL) {
+ free(flags.apply);
+ }
flags.apply = ei_chk_strdup(argv[i+1]);
i++;
break;
@@ -351,7 +431,7 @@ int main(int argc, char *argv[])
/* gethostname requires len to be max(hostname) + 1 */
if (gethostname(h_hostname, EI_MAXHOSTNAMELEN+1) < 0) {
fprintf(stderr,"erl_call: failed to get host name: %d\n", errno);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
if (flags.use_localhost_fallback || (hp = ei_gethostbyname(h_hostname)) == 0) {
@@ -369,7 +449,7 @@ int main(int argc, char *argv[])
if (h_alivename) {
if (strlen(h_alivename) + strlen(h_hostname) + 2 > sizeof(h_nodename_buf)) {
fprintf(stderr,"erl_call: hostname too long: %s\n", h_hostname);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
sprintf(h_nodename, "%s@%s", h_alivename, h_hostname);
}
@@ -383,7 +463,7 @@ int main(int argc, char *argv[])
(short) creation) < 0) {
fprintf(stderr,"erl_call: can't create C node %s; %d\n",
h_nodename, erl_errno);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
}
@@ -409,7 +489,7 @@ int main(int argc, char *argv[])
} else {
if ((hp = ei_gethostbyname(host)) == 0) {
fprintf(stderr,"erl_call: can't ei_gethostbyname(%s)\n", host);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
format_node_hostname(&flags, hp->h_name, host_name);
@@ -418,7 +498,7 @@ int main(int argc, char *argv[])
if (flags.port == -1) {
if (strlen(flags.node) + strlen(host_name) + 2 > sizeof(nodename)) {
fprintf(stderr,"erl_call: nodename too long: %s\n", flags.node);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
sprintf(nodename, "%s@%s", flags.node, host_name);
}
@@ -433,11 +513,11 @@ int main(int argc, char *argv[])
/* We failed to connect ourself */
/* FIXME do we really know we failed because of node not up? */
if (flags.haltp) {
- exit(0);
+ exit_free_flags_fields(0, &flags);
} else {
fprintf(stderr,"erl_call: failed to connect to node %s\n",
nodename);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
}
} else {
@@ -446,12 +526,12 @@ int main(int argc, char *argv[])
/* We failed to connect ourself */
/* FIXME do we really know we failed because of node not up? */
if (flags.haltp) {
- exit(0);
+ exit_free_flags_fields(0, &flags);
} else {
fprintf(stderr,"erl_call: failed to connect to node with address \"%s:%ld\"\n",
flags.hostname == NULL ? "" : flags.hostname,
flags.port);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
}
}
@@ -475,7 +555,7 @@ int main(int argc, char *argv[])
ei_rpc(&ec, fd, "erlang", "halt", p, i, &reply);
free(p);
ei_x_free(&reply);
- exit(0);
+ exit_free_flags_fields(0, &flags);
}
if (flags.verbosep) {
@@ -497,7 +577,7 @@ int main(int argc, char *argv[])
if (strlen(modname) + 4 + 1 > sizeof(fname)) {
fprintf(stderr,"erl_call: module name too long: %s\n", modname);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
strcpy(fname, modname);
strcat(fname, ".erl");
@@ -531,7 +611,7 @@ int main(int argc, char *argv[])
ei_x_free(&reply);
fprintf(stderr,"erl_call: can't write to source file %s\n",
fname);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
free(p);
ei_x_free(&reply);
@@ -596,7 +676,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,19 +692,26 @@ 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);
free(evalbuf); /* Allocated in read_stdin() */
ei_x_free(&reply);
- exit(1);
+ exit_free_flags_fields(1, &flags);
}
- 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 +723,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",
@@ -646,24 +734,38 @@ int main(int argc, char *argv[])
if (ei_x_format_wo_ver(&e, args) < 0) {
/* FIXME no error message and why -1 ? */
- exit(-1);
+ free(mod);
+ free(fun);
+ free(args);
+ exit_free_flags_fields(-1, &flags);
}
-
+ free(args);
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);
+ free(mod);
+ free(fun);
+ exit_free_flags_fields(-1, &flags);
} 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);
}
+ free(mod);
+ free(fun);
}
-
+ exit_free_flags_fields(0, &flags);
return(0);
}
@@ -720,7 +822,7 @@ static int do_connect(ei_cnode *ec, char *nodename, struct call_flags *flags)
if ((r=erl_start_sys(ec,alive,(Erl_IpAddr)(h->h_addr_list[0]),
start_flags,flags->script,args)) < 0) {
fprintf(stderr,"erl_call: unable to start node, error = %d\n", r);
- exit(1);
+ exit_free_flags_fields(1, flags);
}
if ((fd=ei_connect(ec, nodename)) >= 0) {
@@ -734,19 +836,19 @@ static int do_connect(ei_cnode *ec, char *nodename, struct call_flags *flags)
switch (fd) {
case ERL_NO_DAEMON:
fprintf(stderr,"erl_call: no epmd running\n");
- exit(1);
+ exit_free_flags_fields(1, flags);
case ERL_CONNECT_FAIL:
fprintf(stderr,"erl_call: connect failed\n");
- exit(1);
+ exit_free_flags_fields(1, flags);
case ERL_NO_PORT:
fprintf(stderr,"erl_call: node is not running\n");
- exit(1);
+ exit_free_flags_fields(1, flags);
case ERL_TIMEOUT:
fprintf(stderr,"erl_call: connect timed out\n");
- exit(1);
+ exit_free_flags_fields(1, flags);
default:
fprintf(stderr,"erl_call: error during connect\n");
- exit(1);
+ exit_free_flags_fields(1, flags);
}
}
}
@@ -908,6 +1010,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 +1027,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 +1099,690 @@ 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;
+}
+
+
+void exit_free_flags_fields(int exit_status, struct call_flags* flags) {
+ if (flags->script != NULL) {
+ free(flags->script);
+ }
+ if (flags->hidden != NULL) {
+ free(flags->hidden);
+ }
+ if (flags->cookie != NULL) {
+ free(flags->cookie);
+ }
+ if (flags->apply != NULL) {
+ free(flags->apply);
+ }
+ if (flags->hostname != NULL) {
+ free(flags->hostname);
+ }
+ exit(exit_status);
+}
+
+
+/* Constants and helper functions used by erl_start_sys */
+
+/* FIXME is this a case a vfork can be used? */
+#if !HAVE_WORKING_VFORK
+# define vfork fork
+#endif
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 1024
+#endif
+
+#ifndef RSH
+#define RSH "/usr/bin/ssh"
+#endif
+
+#ifndef HAVE_SOCKLEN_T
+typedef int SocklenType;
+#else
+typedef socklen_t SocklenType;
+#endif
+
+/* FIXME check errors from malloc */
+
+static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr);
+
+static int wait_for_erlang(int sockd, int magic, struct timeval *timeout);
+#if defined(__WIN32__)
+static int unique_id(void);
+static HANDLE spawn_erlang_epmd(ei_cnode *ec,
+ char *alive,
+ Erl_IpAddr adr,
+ int flags,
+ char *erl_or_epmd,
+ char *args[],
+ int port,
+ int is_erlang);
+#else
+static int exec_erlang(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
+ char *erl, char *args[],int port);
+#endif
+
+#if defined(__WIN32__)
+#define DEF_ERL_COMMAND "erl"
+#define DEF_EPMD_COMMAND "epmd"
+#define ERL_REPLY_FMT "-s erl_reply reply \"%s\" \"%d\" \"%d\""
+#define ERL_NAME_FMT "-noinput -name %s"
+#define ERL_SNAME_FMT "-noinput -sname %s"
+
+#define IP_ADDR_CHARS 15
+#define FORMATTED_INT_LEN 10
+
+static int unique_id(void){
+ return (int) GetCurrentThreadId();
+}
+
+static int enquote_args(char **oargs, char ***qargs){
+ char **args;
+ int len;
+ int i;
+ int qwhole;
+ int extra;
+ char *ptr;
+ char *ptr2;
+
+ if(oargs == NULL){
+ *qargs = malloc(sizeof(char *));
+ **qargs = NULL;
+ return 0;
+ };
+
+ for(len=0;oargs[len] != NULL; ++len)
+ ;
+ args = malloc(sizeof(char *) * (len + 1));
+
+ for(i = 0; i < len; ++i){
+ qwhole = strchr(oargs[i],' ') != NULL;
+ extra = qwhole * 2;
+ for(ptr = oargs[i]; *ptr != '\0'; ++ptr)
+ extra += (*ptr == '"');
+ args[i] = malloc(strlen(oargs[i]) +
+ extra +
+ 1);
+ ptr2 = args[i];
+ if(qwhole)
+ *(ptr2++) = '"';
+ for(ptr = oargs[i]; *ptr != '\0'; ++ptr){
+ if(*ptr == '"')
+ *(ptr2++) = '\\';
+ *(ptr2++) = *ptr;
+ }
+ if(qwhole)
+ *(ptr2++) = '"';
+ *ptr2 = '\0';
+ }
+ args[len] = NULL;
+ *qargs = args;
+ return len;
+}
+
+static void free_args(char **args){
+ char **ptr = args;
+ while(*ptr != NULL)
+ free(*(ptr++));
+ free(args);
+}
+
+/* In NT we cannot fork(), Erlang and Epmd gets
+ spawned by this function instead. */
+
+static HANDLE spawn_erlang_epmd(ei_cnode *ec,
+ char *alive,
+ Erl_IpAddr adr,
+ int flags,
+ char *erl_or_epmd,
+ char *args[],
+ int port,
+ int is_erlang)
+{
+ STARTUPINFO sinfo;
+ SECURITY_ATTRIBUTES sa;
+ PROCESS_INFORMATION pinfo;
+ char *cmdbuf;
+ int cmdlen;
+ char *ptr;
+ int i;
+ int num_args;
+ char *name_format;
+ struct in_addr myaddr;
+ struct in_addr *hisaddr = (struct in_addr *)adr;
+ char iaddrbuf[IP_ADDR_CHARS + 1];
+ HANDLE ret;
+
+ if(is_erlang){
+ get_addr(ei_thishostname(ec), &myaddr);
+ if((ptr = inet_ntoa(myaddr)) == NULL)
+ return INVALID_HANDLE_VALUE;
+ else
+ strcpy(iaddrbuf,ptr);
+ }
+ if ((flags & ERL_START_REMOTE) ||
+ (is_erlang && (hisaddr->s_addr != myaddr.s_addr))) {
+ return INVALID_HANDLE_VALUE;
+ } else {
+ num_args = enquote_args(args, &args);
+ for(cmdlen = i = 0; args[i] != NULL; ++i)
+ cmdlen += strlen(args[i]) + 1;
+ if(!erl_or_epmd)
+ erl_or_epmd = (is_erlang) ? DEF_ERL_COMMAND :
+ DEF_EPMD_COMMAND;
+ if(is_erlang){
+ name_format = (flags & ERL_START_LONG) ? ERL_NAME_FMT :
+ ERL_SNAME_FMT;
+ cmdlen +=
+ strlen(erl_or_epmd) + (*erl_or_epmd != '\0') +
+ strlen(name_format) + 1 + strlen(alive) +
+ strlen(ERL_REPLY_FMT) + 1 + strlen(iaddrbuf) + 2 * FORMATTED_INT_LEN + 1;
+ ptr = cmdbuf = malloc(cmdlen);
+ if(*erl_or_epmd != '\0')
+ ptr += sprintf(ptr,"%s ",erl_or_epmd);
+ ptr += sprintf(ptr, name_format,
+ alive);
+ ptr += sprintf(ptr, " " ERL_REPLY_FMT,
+ iaddrbuf, port, unique_id());
+ } else { /* epmd */
+ cmdlen += strlen(erl_or_epmd) + (*erl_or_epmd != '\0') + 1;
+ ptr = cmdbuf = malloc(cmdlen);
+ if(*erl_or_epmd != '\0')
+ ptr += sprintf(ptr,"%s ",erl_or_epmd);
+ else
+ *(ptr++) = '\0';
+ }
+ for(i= 0; args[i] != NULL; ++i){
+ *(ptr++) = ' ';
+ strcpy(ptr,args[i]);
+ ptr += strlen(args[i]);
+ }
+ free_args(args);
+ if (flags & ERL_START_VERBOSE) {
+ fprintf(stderr,"erl_call: commands are %s\n",cmdbuf);
+ }
+ /* OK, one single command line... */
+ /* Hmmm, hidden or unhidden window??? */
+ memset(&sinfo,0,sizeof(sinfo));
+ sinfo.cb = sizeof(STARTUPINFO);
+ sinfo.dwFlags = STARTF_USESHOWWINDOW /*|
+ STARTF_USESTDHANDLES*/;
+ sinfo.wShowWindow = SW_HIDE; /* Hidden! */
+ sinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ sinfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ sinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = /*TRUE*/ FALSE;
+ if(!CreateProcess(
+ NULL,
+ cmdbuf,
+ &sa,
+ NULL,
+ /*TRUE*/ FALSE,
+ 0 | CREATE_NEW_CONSOLE,
+ NULL,
+ NULL,
+ &sinfo,
+ &pinfo))
+ ret = INVALID_HANDLE_VALUE;
+ else
+ ret = pinfo.hProcess;
+ free(cmdbuf);
+ return ret;
+ }
+ /* NOTREACHED */
+}
+#else /* Unix */
+
+/* call this from the child process to start an erlang system. This
+ * function just builds the erlang command line and then calls it.
+ *
+ * node - the nodename for the new node
+ * flags - various options that can be set (see erl_start.h)
+ * erl - name of the erlang executable, or NULL for default ("erl")
+ * args - additional arguments to pass to erlang executable
+ * port - the port number where we wait for acknowledgment from the enode
+ *
+ * we have a potential problem if args conflicts with any of the
+ * arguments we use here.
+ */
+static int exec_erlang(ei_cnode *ec,
+ char *alive,
+ Erl_IpAddr adr,
+ int flags,
+ char *erl,
+ char *args[],
+ int port)
+{
+#if !defined(__WIN32__)
+ int fd,len,l,i;
+ char **s;
+ char *argv[4];
+ char argbuf[BUFSIZ];
+ struct in_addr myaddr;
+ struct in_addr *hisaddr = (struct in_addr *)adr;
+
+ if (!get_addr(ei_thishostname(ec), &myaddr)) {
+ fprintf(stderr,"erl_call: failed to find hostname\r\n");
+ return ERL_SYS_ERROR;
+ }
+
+ /* on this host? */
+ /* compare ip addresses, unless forced by flag setting to use rsh */
+ if ((flags & ERL_START_REMOTE) || (hisaddr->s_addr != myaddr.s_addr)) {
+ argv[0] = RSH;
+ len = strlen(inet_ntoa(*hisaddr));
+ argv[1] = malloc(len+1);
+ strcpy(argv[1],inet_ntoa(*hisaddr));
+ }
+ else {
+ /* Yes - use sh to start local Erlang */
+ argv[0] = "sh";
+ argv[1] = "-c";
+ }
+ argv[2] = argbuf;
+ argv[3] = NULL;
+
+ len = 0;
+ *argbuf=(char)0;
+
+ sprintf(argbuf,"exec %s ", (erl? erl: "erl"));
+ len = strlen(argbuf);
+
+ /* *must* be noinput or node (seems to) hang... */
+ /* long or short names? */
+ sprintf(&argbuf[len], "-noinput %s %s ",
+ ((flags & ERL_START_LONG) ? "-name" : "-sname"),
+ alive);
+ len = strlen(argbuf);
+
+ /* now make the new node report back when it's ready */
+ /* add: myip, myport and replymsg */
+ sprintf(&argbuf[len],
+ "-s erl_reply reply %s %d %d ",
+ inet_ntoa(myaddr),port,(int)getpid());
+#ifdef DEBUG
+ fprintf(stderr,"erl_call: debug %s\n",&argbuf[len]);
+#endif
+ len = strlen(argbuf);
+
+ /* additional arguments to be passed to the other system */
+ /* make sure that they will fit first */
+ for (l=0, s = args; s && *s; s++) l+= strlen(*s) + 1;
+
+ if (len + l + 1 > BUFSIZ) return ERL_BADARG;
+ else {
+ for (s = args; s && *s; s++) {
+ strcat(argbuf," ");
+ strcat(argbuf,*s);
+ }
+ len += l + 1;
+ }
+
+ if (flags & ERL_START_VERBOSE) {
+ fprintf(stderr,"erl_call: %s %s %s\n",argv[0],argv[1],argv[2]);
+ }
+
+ /* close all descriptors in child */
+ for (i=0; i<64; i++) close(i);
+
+ /* debug output to file? */
+ if (flags & ERL_START_DEBUG) {
+ char debugfile[MAXPATHLEN+1];
+ char *home=getenv("HOME");
+ sprintf(debugfile,"%s/%s.%s",home,ERL_START_LOGFILE,alive);
+ if ((fd=open(debugfile, O_WRONLY | O_CREAT | O_APPEND, 0644)) >= 0) {
+ time_t t = time(NULL);
+ dup2(fd,1);
+ dup2(fd,2);
+ fprintf(stderr,"\n\n===== Log started ======\n%s \n",ctime(&t));
+ fprintf(stderr,"erl_call: %s %s %s\n",argv[0],argv[1],argv[2]);
+ }
+ }
+
+ /* start the system */
+ execvp(argv[0], argv);
+
+ if (flags & ERL_START_DEBUG) {
+ fprintf(stderr,"erl_call: exec failed: (%d) %s %s %s\n",
+ errno,argv[0],argv[1],argv[2]);
+ }
+
+#endif
+ /* (hopefully) NOT REACHED */
+ return ERL_SYS_ERROR;
+} /* exec_erlang() */
+
+#endif /* defined(WINDOWS) */
+
+#if defined(__WIN32__)
+static void gettimeofday(struct timeval *now,void *dummy){
+ SYSTEMTIME systime;
+ FILETIME ft;
+ DWORD x;
+ GetSystemTime(&systime);
+ SystemTimeToFileTime(&systime,&ft);
+ x = ft.dwLowDateTime / 10;
+ now->tv_sec = x / 1000000;
+ now->tv_usec = x % 1000000;
+}
+
+#endif
+
+
+/* wait for the remote system to reply */
+/*
+ * sockd - an open socket where we expect a connection from the e-node
+ * magic - sign on message the e-node must provide for verification
+ * timeout - how long to wait before returning failure
+ *
+ * OBS: the socket is blocking, and there is a potential deadlock if we
+ * get an accept but the peer sends no data (and does not close).
+ * in normal cases the timeout will work ok however, i.e. either we
+ * never get any connection, or we get connection then close().
+ */
+static int wait_for_erlang(int sockd, int magic, struct timeval *timeout)
+{
+ struct timeval to;
+ struct timeval stop_time;
+ struct timeval now;
+ fd_set rdset;
+ int fd;
+ int n,i;
+ char buf[16];
+ struct sockaddr_in peer;
+ SocklenType len = (SocklenType) sizeof(peer);
+
+ /* determine when we should exit this function */
+ gettimeofday(&now,NULL);
+ stop_time.tv_sec = now.tv_sec + timeout->tv_sec;
+ stop_time.tv_usec = now.tv_usec + timeout->tv_usec;
+ while (stop_time.tv_usec > 1000000) {
+ stop_time.tv_sec++;
+ stop_time.tv_usec -= 1000000;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr,"erl_call: debug time is %ld.%06ld, "
+ "will timeout at %ld.%06ld\n",
+ now.tv_sec,now.tv_usec,stop_time.tv_sec,stop_time.tv_usec);
+#endif
+
+ while (1) {
+ FD_ZERO(&rdset);
+ FD_SET(sockd,&rdset);
+
+ /* adjust the timeout to (stoptime - now) */
+ gettimeofday(&now,NULL);
+ to.tv_sec = stop_time.tv_sec - now.tv_sec;
+ to.tv_usec = stop_time.tv_usec - now.tv_usec;
+ while ((to.tv_usec < 0) && (to.tv_sec > 0)) {
+ to.tv_usec += 1000000;
+ to.tv_sec--;
+ }
+ if (to.tv_sec < 0) return ERL_TIMEOUT;
+
+#ifdef DEBUG
+ fprintf(stderr,"erl_call: debug remaining to timeout: %ld.%06ld\n",
+ to.tv_sec,to.tv_usec);
+#endif
+ switch ((i = select(sockd+1,&rdset,NULL,NULL,&to))) {
+ case -1:
+ return ERL_SYS_ERROR;
+ break;
+
+ case 0: /* timeout */
+#ifdef DEBUG
+ gettimeofday(&now,NULL);
+ fprintf(stderr,"erl_call: debug timed out at %ld.%06ld\n",
+ now.tv_sec,now.tv_usec);
+#endif
+ return ERL_TIMEOUT;
+ break;
+
+ default: /* ready descriptors */
+#ifdef DEBUG
+ gettimeofday(&now,NULL);
+ fprintf(stderr,"erl_call: debug got select at %ld.%06ld\n",
+ now.tv_sec,now.tv_usec);
+#endif
+ if (FD_ISSET(sockd,&rdset)) {
+ if ((fd = accept(sockd,(struct sockaddr *)&peer,&len)) < 0)
+ return ERL_SYS_ERROR;
+
+ /* now get sign-on message and terminate it */
+#if defined(__WIN32__)
+ if ((n=recv(fd,buf,16,0)) >= 0) buf[n]=0x0;
+ closesocket(fd);
+#else
+ if ((n=read(fd,buf,16)) >= 0) buf[n]=0x0;
+ close(fd);
+#endif
+#ifdef DEBUG
+ fprintf(stderr,"erl_call: debug got %d, expected %d\n",
+ atoi(buf),magic);
+#endif
+ if (atoi(buf) == magic) return 0; /* success */
+ } /* if FD_SET */
+ } /* switch */
+ } /* while */
+
+ /* unreached? */
+ return ERL_SYS_ERROR;
+} /* wait_for_erlang() */
+
+
+static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr)
+{
+ struct hostent *hp;
+
+#if !defined (__WIN32__)
+ char buf[1024];
+ struct hostent host;
+ int herror;
+
+ hp = ei_gethostbyname_r(hostname,&host,buf,1024,&herror);
+#else
+ hp = ei_gethostbyname(hostname);
+#endif
+
+ if (hp) {
+ memmove(oaddr,hp->h_addr_list[0],sizeof(*oaddr));
+ return oaddr;
+ }
+ return NULL;
+}
+
+/* Start an Erlang node. return value 0 indicates that node was
+ * started successfully, negative values indicate error.
+ *
+ * node - the name of the remote node to start (alivename@hostname).
+ * flags - turn on or off certain options. See erl_start.h for a list.
+ * erl - is the name of the erl script to call. If NULL, the default
+ * name "erl" will be used.
+ * args - a NULL-terminated list of strings containing
+ * additional arguments to be sent to the remote Erlang node. These
+ * strings are simply appended to the end of the command line, so any
+ * quoting of special characters, etc must be done by the caller.
+ * There may be some conflicts between some of these arguments and the
+ * default arguments hard-coded into this function, so be careful.
+ */
+int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
+ char *erl, char *args[])
+{
+ struct timeval timeout;
+ struct sockaddr_in addr;
+ SocklenType namelen;
+ int port;
+ int sockd = 0;
+ int one = 1;
+#if defined(__WIN32__)
+ HANDLE pid;
+#else
+ int pid;
+#endif
+ int r = 0;
+
+ if (((sockd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
+ (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0)) {
+ r = ERL_SYS_ERROR;
+ goto done;
+ }
+
+ memset(&addr,0,sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = 0;
+
+ if (bind(sockd,(struct sockaddr *)&addr,sizeof(addr))<0) {
+ return ERL_SYS_ERROR;
+ }
+ namelen = sizeof(addr);
+ if (getsockname(sockd,(struct sockaddr *)&addr,&namelen)<0) {
+ return ERL_SYS_ERROR;
+ }
+ port = ntohs(addr.sin_port);
+
+ listen(sockd,5);
+
+#if defined(__WIN32__)
+ pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1);
+ if (pid == INVALID_HANDLE_VALUE)
+ return ERL_SYS_ERROR;
+ timeout.tv_usec = 0;
+ timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
+ if((r = wait_for_erlang(sockd,unique_id(),&timeout))
+ == ERL_TIMEOUT) {
+ /* Well, this is not a nice way to do it, and it does not
+ always kill the emulator, but the alternatives are few.*/
+ TerminateProcess(pid,1);
+ }
+#else /* Unix */
+ switch ((pid = fork())) {
+ case -1:
+ r = ERL_SYS_ERROR;
+ break;
+
+ case 0:
+ /* child - start the erlang node */
+ exec_erlang(ec, alive, adr, flags, erl, args, port);
+
+ /* error if reached - parent reports back to caller after timeout
+ so we just exit here */
+ exit(1);
+ break;
+
+ default:
+
+ /* parent - waits for response from Erlang node */
+ /* child pid used here as magic number */
+ timeout.tv_usec = 0;
+ timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
+ if ((r = wait_for_erlang(sockd,pid,&timeout)) == ERL_TIMEOUT) {
+ /* kill child if no response */
+ kill(pid,SIGINT);
+ sleep(1);
+ if (waitpid(pid,NULL,WNOHANG) != pid) {
+ /* no luck - try harder */
+ kill(pid,SIGKILL);
+ sleep(1);
+ waitpid(pid,NULL,WNOHANG);
+ }
+ }
+
+ }
+#endif /* defined(__WIN32__) */
+
+done:
+#if defined(__WIN32__)
+ if (sockd) closesocket(sockd);
+#else
+ if (sockd) close(sockd);
+#endif
+ return r;
+} /* erl_start_sys() */
diff --git a/lib/erl_interface/src/prog/erl_start.c b/lib/erl_interface/src/prog/erl_start.c
deleted file mode 100644
index 9c876feb5e..0000000000
--- a/lib/erl_interface/src/prog/erl_start.c
+++ /dev/null
@@ -1,634 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2020. 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%
- *
- */
-
-/* An exception from using eidef.h, use config.h directly */
-#include "config.h"
-
-#include <stdlib.h>
-#include <sys/types.h>
-#include <fcntl.h>
-
-#ifdef __WIN32__
-#include <winsock2.h>
-#include <windows.h>
-#include <winbase.h>
-
-#else /* unix */
-#include <errno.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/param.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <sys/time.h>
-#include <time.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <signal.h>
-#endif
-
-#include "ei.h"
-#include "ei_resolve.h"
-#include "erl_start.h"
-
-/* FIXME is this a case a vfork can be used? */
-#if !HAVE_WORKING_VFORK
-# define vfork fork
-#endif
-
-#ifndef MAXPATHLEN
-#define MAXPATHLEN 1024
-#endif
-
-#ifndef RSH
-#define RSH "/usr/bin/ssh"
-#endif
-
-#ifndef HAVE_SOCKLEN_T
-typedef int SocklenType;
-#else
-typedef socklen_t SocklenType;
-#endif
-
-/* FIXME check errors from malloc */
-
-static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr);
-
-static int wait_for_erlang(int sockd, int magic, struct timeval *timeout);
-#if defined(__WIN32__)
-static int unique_id(void);
-static HANDLE spawn_erlang_epmd(ei_cnode *ec,
- char *alive,
- Erl_IpAddr adr,
- int flags,
- char *erl_or_epmd,
- char *args[],
- int port,
- int is_erlang);
-#else
-static int exec_erlang(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
- char *erl, char *args[],int port);
-#endif
-/* Start an Erlang node. return value 0 indicates that node was
- * started successfully, negative values indicate error.
- *
- * node - the name of the remote node to start (alivename@hostname).
- * flags - turn on or off certain options. See erl_start.h for a list.
- * erl - is the name of the erl script to call. If NULL, the default
- * name "erl" will be used.
- * args - a NULL-terminated list of strings containing
- * additional arguments to be sent to the remote Erlang node. These
- * strings are simply appended to the end of the command line, so any
- * quoting of special characters, etc must be done by the caller.
- * There may be some conflicts between some of these arguments and the
- * default arguments hard-coded into this function, so be careful.
- */
-int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
- char *erl, char *args[])
-{
- struct timeval timeout;
- struct sockaddr_in addr;
- SocklenType namelen;
- int port;
- int sockd = 0;
- int one = 1;
-#if defined(__WIN32__)
- HANDLE pid;
-#else
- int pid;
-#endif
- int r = 0;
-
- if (((sockd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
- (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0)) {
- r = ERL_SYS_ERROR;
- goto done;
- }
-
- memset(&addr,0,sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
- addr.sin_port = 0;
-
- if (bind(sockd,(struct sockaddr *)&addr,sizeof(addr))<0) {
- return ERL_SYS_ERROR;
- }
- namelen = sizeof(addr);
- if (getsockname(sockd,(struct sockaddr *)&addr,&namelen)<0) {
- return ERL_SYS_ERROR;
- }
- port = ntohs(addr.sin_port);
-
- listen(sockd,5);
-
-#if defined(__WIN32__)
- pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1);
- if (pid == INVALID_HANDLE_VALUE)
- return ERL_SYS_ERROR;
- timeout.tv_usec = 0;
- timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
- if((r = wait_for_erlang(sockd,unique_id(),&timeout))
- == ERL_TIMEOUT) {
- /* Well, this is not a nice way to do it, and it does not
- always kill the emulator, but the alternatives are few.*/
- TerminateProcess(pid,1);
- }
-#else /* Unix */
- switch ((pid = fork())) {
- case -1:
- r = ERL_SYS_ERROR;
- break;
-
- case 0:
- /* child - start the erlang node */
- exec_erlang(ec, alive, adr, flags, erl, args, port);
-
- /* error if reached - parent reports back to caller after timeout
- so we just exit here */
- exit(1);
- break;
-
- default:
-
- /* parent - waits for response from Erlang node */
- /* child pid used here as magic number */
- timeout.tv_usec = 0;
- timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
- if ((r = wait_for_erlang(sockd,pid,&timeout)) == ERL_TIMEOUT) {
- /* kill child if no response */
- kill(pid,SIGINT);
- sleep(1);
- if (waitpid(pid,NULL,WNOHANG) != pid) {
- /* no luck - try harder */
- kill(pid,SIGKILL);
- sleep(1);
- waitpid(pid,NULL,WNOHANG);
- }
- }
-
- }
-#endif /* defined(__WIN32__) */
-
-done:
-#if defined(__WIN32__)
- if (sockd) closesocket(sockd);
-#else
- if (sockd) close(sockd);
-#endif
- return r;
-} /* erl_start_sys() */
-
-#if defined(__WIN32__)
-#define DEF_ERL_COMMAND "erl"
-#define DEF_EPMD_COMMAND "epmd"
-#define ERL_REPLY_FMT "-s erl_reply reply \"%s\" \"%d\" \"%d\""
-#define ERL_NAME_FMT "-noinput -name %s"
-#define ERL_SNAME_FMT "-noinput -sname %s"
-
-#define IP_ADDR_CHARS 15
-#define FORMATTED_INT_LEN 10
-
-static int unique_id(void){
- return (int) GetCurrentThreadId();
-}
-
-static int enquote_args(char **oargs, char ***qargs){
- char **args;
- int len;
- int i;
- int qwhole;
- int extra;
- char *ptr;
- char *ptr2;
-
- if(oargs == NULL){
- *qargs = malloc(sizeof(char *));
- **qargs = NULL;
- return 0;
- };
-
- for(len=0;oargs[len] != NULL; ++len)
- ;
- args = malloc(sizeof(char *) * (len + 1));
-
- for(i = 0; i < len; ++i){
- qwhole = strchr(oargs[i],' ') != NULL;
- extra = qwhole * 2;
- for(ptr = oargs[i]; *ptr != '\0'; ++ptr)
- extra += (*ptr == '"');
- args[i] = malloc(strlen(oargs[i]) +
- extra +
- 1);
- ptr2 = args[i];
- if(qwhole)
- *(ptr2++) = '"';
- for(ptr = oargs[i]; *ptr != '\0'; ++ptr){
- if(*ptr == '"')
- *(ptr2++) = '\\';
- *(ptr2++) = *ptr;
- }
- if(qwhole)
- *(ptr2++) = '"';
- *ptr2 = '\0';
- }
- args[len] = NULL;
- *qargs = args;
- return len;
-}
-
-static void free_args(char **args){
- char **ptr = args;
- while(*ptr != NULL)
- free(*(ptr++));
- free(args);
-}
-
-/* In NT we cannot fork(), Erlang and Epmd gets
- spawned by this function instead. */
-
-static HANDLE spawn_erlang_epmd(ei_cnode *ec,
- char *alive,
- Erl_IpAddr adr,
- int flags,
- char *erl_or_epmd,
- char *args[],
- int port,
- int is_erlang)
-{
- STARTUPINFO sinfo;
- SECURITY_ATTRIBUTES sa;
- PROCESS_INFORMATION pinfo;
- char *cmdbuf;
- int cmdlen;
- char *ptr;
- int i;
- int num_args;
- char *name_format;
- struct in_addr myaddr;
- struct in_addr *hisaddr = (struct in_addr *)adr;
- char iaddrbuf[IP_ADDR_CHARS + 1];
- HANDLE ret;
-
- if(is_erlang){
- get_addr(ei_thishostname(ec), &myaddr);
- if((ptr = inet_ntoa(myaddr)) == NULL)
- return INVALID_HANDLE_VALUE;
- else
- strcpy(iaddrbuf,ptr);
- }
- if ((flags & ERL_START_REMOTE) ||
- (is_erlang && (hisaddr->s_addr != myaddr.s_addr))) {
- return INVALID_HANDLE_VALUE;
- } else {
- num_args = enquote_args(args, &args);
- for(cmdlen = i = 0; args[i] != NULL; ++i)
- cmdlen += strlen(args[i]) + 1;
- if(!erl_or_epmd)
- erl_or_epmd = (is_erlang) ? DEF_ERL_COMMAND :
- DEF_EPMD_COMMAND;
- if(is_erlang){
- name_format = (flags & ERL_START_LONG) ? ERL_NAME_FMT :
- ERL_SNAME_FMT;
- cmdlen +=
- strlen(erl_or_epmd) + (*erl_or_epmd != '\0') +
- strlen(name_format) + 1 + strlen(alive) +
- strlen(ERL_REPLY_FMT) + 1 + strlen(iaddrbuf) + 2 * FORMATTED_INT_LEN + 1;
- ptr = cmdbuf = malloc(cmdlen);
- if(*erl_or_epmd != '\0')
- ptr += sprintf(ptr,"%s ",erl_or_epmd);
- ptr += sprintf(ptr, name_format,
- alive);
- ptr += sprintf(ptr, " " ERL_REPLY_FMT,
- iaddrbuf, port, unique_id());
- } else { /* epmd */
- cmdlen += strlen(erl_or_epmd) + (*erl_or_epmd != '\0') + 1;
- ptr = cmdbuf = malloc(cmdlen);
- if(*erl_or_epmd != '\0')
- ptr += sprintf(ptr,"%s ",erl_or_epmd);
- else
- *(ptr++) = '\0';
- }
- for(i= 0; args[i] != NULL; ++i){
- *(ptr++) = ' ';
- strcpy(ptr,args[i]);
- ptr += strlen(args[i]);
- }
- free_args(args);
- if (flags & ERL_START_VERBOSE) {
- fprintf(stderr,"erl_call: commands are %s\n",cmdbuf);
- }
- /* OK, one single command line... */
- /* Hmmm, hidden or unhidden window??? */
- memset(&sinfo,0,sizeof(sinfo));
- sinfo.cb = sizeof(STARTUPINFO);
- sinfo.dwFlags = STARTF_USESHOWWINDOW /*|
- STARTF_USESTDHANDLES*/;
- sinfo.wShowWindow = SW_HIDE; /* Hidden! */
- sinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
- sinfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
- sinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
- sa.nLength = sizeof(sa);
- sa.lpSecurityDescriptor = NULL;
- sa.bInheritHandle = /*TRUE*/ FALSE;
- if(!CreateProcess(
- NULL,
- cmdbuf,
- &sa,
- NULL,
- /*TRUE*/ FALSE,
- 0 | CREATE_NEW_CONSOLE,
- NULL,
- NULL,
- &sinfo,
- &pinfo))
- ret = INVALID_HANDLE_VALUE;
- else
- ret = pinfo.hProcess;
- free(cmdbuf);
- return ret;
- }
- /* NOTREACHED */
-}
-#else /* Unix */
-
-/* call this from the child process to start an erlang system. This
- * function just builds the erlang command line and then calls it.
- *
- * node - the nodename for the new node
- * flags - various options that can be set (see erl_start.h)
- * erl - name of the erlang executable, or NULL for default ("erl")
- * args - additional arguments to pass to erlang executable
- * port - the port number where we wait for acknowledgment from the enode
- *
- * we have a potential problem if args conflicts with any of the
- * arguments we use here.
- */
-static int exec_erlang(ei_cnode *ec,
- char *alive,
- Erl_IpAddr adr,
- int flags,
- char *erl,
- char *args[],
- int port)
-{
-#if !defined(__WIN32__)
- int fd,len,l,i;
- char **s;
- char *argv[4];
- char argbuf[BUFSIZ];
- struct in_addr myaddr;
- struct in_addr *hisaddr = (struct in_addr *)adr;
-
- if (!get_addr(ei_thishostname(ec), &myaddr)) {
- fprintf(stderr,"erl_call: failed to find hostname\r\n");
- return ERL_SYS_ERROR;
- }
-
- /* on this host? */
- /* compare ip addresses, unless forced by flag setting to use rsh */
- if ((flags & ERL_START_REMOTE) || (hisaddr->s_addr != myaddr.s_addr)) {
- argv[0] = RSH;
- len = strlen(inet_ntoa(*hisaddr));
- argv[1] = malloc(len+1);
- strcpy(argv[1],inet_ntoa(*hisaddr));
- }
- else {
- /* Yes - use sh to start local Erlang */
- argv[0] = "sh";
- argv[1] = "-c";
- }
- argv[2] = argbuf;
- argv[3] = NULL;
-
- len = 0;
- *argbuf=(char)0;
-
- sprintf(argbuf,"exec %s ", (erl? erl: "erl"));
- len = strlen(argbuf);
-
- /* *must* be noinput or node (seems to) hang... */
- /* long or short names? */
- sprintf(&argbuf[len], "-noinput %s %s ",
- ((flags & ERL_START_LONG) ? "-name" : "-sname"),
- alive);
- len = strlen(argbuf);
-
- /* now make the new node report back when it's ready */
- /* add: myip, myport and replymsg */
- sprintf(&argbuf[len],
- "-s erl_reply reply %s %d %d ",
- inet_ntoa(myaddr),port,(int)getpid());
-#ifdef DEBUG
- fprintf(stderr,"erl_call: debug %s\n",&argbuf[len]);
-#endif
- len = strlen(argbuf);
-
- /* additional arguments to be passed to the other system */
- /* make sure that they will fit first */
- for (l=0, s = args; s && *s; s++) l+= strlen(*s) + 1;
-
- if (len + l + 1 > BUFSIZ) return ERL_BADARG;
- else {
- for (s = args; s && *s; s++) {
- strcat(argbuf," ");
- strcat(argbuf,*s);
- }
- len += l + 1;
- }
-
- if (flags & ERL_START_VERBOSE) {
- fprintf(stderr,"erl_call: %s %s %s\n",argv[0],argv[1],argv[2]);
- }
-
- /* close all descriptors in child */
- for (i=0; i<64; i++) close(i);
-
- /* debug output to file? */
- if (flags & ERL_START_DEBUG) {
- char debugfile[MAXPATHLEN+1];
- char *home=getenv("HOME");
- sprintf(debugfile,"%s/%s.%s",home,ERL_START_LOGFILE,alive);
- if ((fd=open(debugfile, O_WRONLY | O_CREAT | O_APPEND, 0644)) >= 0) {
- time_t t = time(NULL);
- dup2(fd,1);
- dup2(fd,2);
- fprintf(stderr,"\n\n===== Log started ======\n%s \n",ctime(&t));
- fprintf(stderr,"erl_call: %s %s %s\n",argv[0],argv[1],argv[2]);
- }
- }
-
- /* start the system */
- execvp(argv[0], argv);
-
- if (flags & ERL_START_DEBUG) {
- fprintf(stderr,"erl_call: exec failed: (%d) %s %s %s\n",
- errno,argv[0],argv[1],argv[2]);
- }
-
-#endif
- /* (hopefully) NOT REACHED */
- return ERL_SYS_ERROR;
-} /* exec_erlang() */
-
-#endif /* defined(WINDOWS) */
-
-#if defined(__WIN32__)
-static void gettimeofday(struct timeval *now,void *dummy){
- SYSTEMTIME systime;
- FILETIME ft;
- DWORD x;
- GetSystemTime(&systime);
- SystemTimeToFileTime(&systime,&ft);
- x = ft.dwLowDateTime / 10;
- now->tv_sec = x / 1000000;
- now->tv_usec = x % 1000000;
-}
-
-#endif
-
-
-/* wait for the remote system to reply */
-/*
- * sockd - an open socket where we expect a connection from the e-node
- * magic - sign on message the e-node must provide for verification
- * timeout - how long to wait before returning failure
- *
- * OBS: the socket is blocking, and there is a potential deadlock if we
- * get an accept but the peer sends no data (and does not close).
- * in normal cases the timeout will work ok however, i.e. either we
- * never get any connection, or we get connection then close().
- */
-static int wait_for_erlang(int sockd, int magic, struct timeval *timeout)
-{
- struct timeval to;
- struct timeval stop_time;
- struct timeval now;
- fd_set rdset;
- int fd;
- int n,i;
- char buf[16];
- struct sockaddr_in peer;
- SocklenType len = (SocklenType) sizeof(peer);
-
- /* determine when we should exit this function */
- gettimeofday(&now,NULL);
- stop_time.tv_sec = now.tv_sec + timeout->tv_sec;
- stop_time.tv_usec = now.tv_usec + timeout->tv_usec;
- while (stop_time.tv_usec > 1000000) {
- stop_time.tv_sec++;
- stop_time.tv_usec -= 1000000;
- }
-
-#ifdef DEBUG
- fprintf(stderr,"erl_call: debug time is %ld.%06ld, "
- "will timeout at %ld.%06ld\n",
- now.tv_sec,now.tv_usec,stop_time.tv_sec,stop_time.tv_usec);
-#endif
-
- while (1) {
- FD_ZERO(&rdset);
- FD_SET(sockd,&rdset);
-
- /* adjust the timeout to (stoptime - now) */
- gettimeofday(&now,NULL);
- to.tv_sec = stop_time.tv_sec - now.tv_sec;
- to.tv_usec = stop_time.tv_usec - now.tv_usec;
- while ((to.tv_usec < 0) && (to.tv_sec > 0)) {
- to.tv_usec += 1000000;
- to.tv_sec--;
- }
- if (to.tv_sec < 0) return ERL_TIMEOUT;
-
-#ifdef DEBUG
- fprintf(stderr,"erl_call: debug remaining to timeout: %ld.%06ld\n",
- to.tv_sec,to.tv_usec);
-#endif
- switch ((i = select(sockd+1,&rdset,NULL,NULL,&to))) {
- case -1:
- return ERL_SYS_ERROR;
- break;
-
- case 0: /* timeout */
-#ifdef DEBUG
- gettimeofday(&now,NULL);
- fprintf(stderr,"erl_call: debug timed out at %ld.%06ld\n",
- now.tv_sec,now.tv_usec);
-#endif
- return ERL_TIMEOUT;
- break;
-
- default: /* ready descriptors */
-#ifdef DEBUG
- gettimeofday(&now,NULL);
- fprintf(stderr,"erl_call: debug got select at %ld.%06ld\n",
- now.tv_sec,now.tv_usec);
-#endif
- if (FD_ISSET(sockd,&rdset)) {
- if ((fd = accept(sockd,(struct sockaddr *)&peer,&len)) < 0)
- return ERL_SYS_ERROR;
-
- /* now get sign-on message and terminate it */
-#if defined(__WIN32__)
- if ((n=recv(fd,buf,16,0)) >= 0) buf[n]=0x0;
- closesocket(fd);
-#else
- if ((n=read(fd,buf,16)) >= 0) buf[n]=0x0;
- close(fd);
-#endif
-#ifdef DEBUG
- fprintf(stderr,"erl_call: debug got %d, expected %d\n",
- atoi(buf),magic);
-#endif
- if (atoi(buf) == magic) return 0; /* success */
- } /* if FD_SET */
- } /* switch */
- } /* while */
-
- /* unreached? */
- return ERL_SYS_ERROR;
-} /* wait_for_erlang() */
-
-
-static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr)
-{
- struct hostent *hp;
-
-#if !defined (__WIN32__)
- char buf[1024];
- struct hostent host;
- int herror;
-
- hp = ei_gethostbyname_r(hostname,&host,buf,1024,&herror);
-#else
- hp = ei_gethostbyname(hostname);
-#endif
-
- if (hp) {
- memmove(oaddr,hp->h_addr_list[0],sizeof(*oaddr));
- return oaddr;
- }
- return NULL;
-}
diff --git a/lib/erl_interface/src/prog/erl_start.h b/lib/erl_interface/src/prog/erl_start.h
deleted file mode 100644
index 1d0d584c45..0000000000
--- a/lib/erl_interface/src/prog/erl_start.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. 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%
- *
-
- */
-#ifndef _ERL_START_H
-#define _ERL_START_H
-
-#define ERL_START_MSG "gurka" /* make something up */
-#define ERL_START_TIME 10000 /* wait this long (ms) */
-#define ERL_START_LOGFILE ".erl_start.out" /* basename of logfile */
-
-/* flags used by erl_connect and erl_xconnect */
-#define ERL_START_ENODE 0x0001
-#define ERL_START_EPMD 0x0002
-#define ERL_START_LONG 0x0004
-#define ERL_START_COOKIE 0x0008
-#define ERL_START_DEBUG 0x0010
-#define ERL_START_VERBOSE 0x0020
-#define ERL_START_REMOTE 0x0040
-
-/* error return values */
-#define ERL_S_TIMEOUT -51 /* a timeout occurred */
-#define ERL_BADARG -52 /* an argument contained an incorrect value */
-#define ERL_SYS_ERROR -99 /* a system error occurred (check errno) */
-
-/* start an erlang system */
-int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr addr, int flags,
- char *erl, char *add_args[]);
-
-#endif /* _ERL_START_H */
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/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl
index 002a069a92..71f3765f21 100644
--- a/lib/eunit/src/eunit_surefire.erl
+++ b/lib/eunit/src/eunit_surefire.erl
@@ -95,6 +95,7 @@ start(Options) ->
init(Options) ->
XMLDir = proplists:get_value(dir, Options, ?XMLDIR),
+ ensure_xmldir(XMLDir),
St = #state{verbose = proplists:get_bool(verbose, Options),
xmldir = XMLDir,
testsuites = []},
@@ -255,6 +256,19 @@ add_testcase_to_testsuite({error, Exception}, TestCaseTmp, TestSuite) ->
testcases = [TestCase|TestSuite#testsuite.testcases] }
end.
+ensure_xmldir(XMLDir) ->
+ Steps = [
+ fun filelib:ensure_dir/1,
+ fun file:make_dir/1],
+ lists:foldl(fun ensure_xmldir/2, XMLDir, Steps).
+
+ensure_xmldir(Fun, XMLDir) ->
+ case Fun(XMLDir) of
+ ok -> XMLDir;
+ {error, eexist} -> XMLDir;
+ {error, _Reason} = Error -> throw(Error)
+ end.
+
%% ----------------------------------------------------------------------------
%% Write a report to the XML directory.
%% This function opens the report file, calls write_report_to/2 and closes the file.
diff --git a/lib/eunit/test/eunit_SUITE.erl b/lib/eunit/test/eunit_SUITE.erl
index e55091e8c1..df754e46c5 100644
--- a/lib/eunit/test/eunit_SUITE.erl
+++ b/lib/eunit/test/eunit_SUITE.erl
@@ -22,7 +22,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
app_test/1,appup_test/1,eunit_test/1,surefire_utf8_test/1,surefire_latin_test/1,
- surefire_c0_test/1]).
+ surefire_c0_test/1, surefire_ensure_dir_test/1]).
-include_lib("common_test/include/ct.hrl").
@@ -30,7 +30,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app_test, appup_test, eunit_test, surefire_utf8_test, surefire_latin_test,
- surefire_c0_test].
+ surefire_c0_test, surefire_ensure_dir_test].
groups() ->
[].
@@ -76,6 +76,11 @@ surefire_c0_test(Config) when is_list(Config) ->
true = lists:member($\t, Chars),
ok.
+surefire_ensure_dir_test(Config) when is_list(Config) ->
+ XMLDir = filename:join(proplists:get_value(priv_dir, Config), "c1"),
+ ok = eunit:test(tc0, [{report,{eunit_surefire,[{dir,XMLDir}]}}]),
+ ok = file:del_dir_r(XMLDir).
+
check_surefire(Module) ->
File = "TEST-"++atom_to_list(Module)++".xml",
file:delete(File),
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 562384f7be..479c9699b5 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -670,6 +670,22 @@
</section>
+<section><title>Kernel 6.5.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>When running Xref in the <c>modules</c> mode, the
+ Debugger application would show up as a depency for the
+ Kernel applications.</p>
+ <p>
+ Own Id: OTP-17223 Aux Id: GH-4546, PR-4554 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.5.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
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).
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index e2aa35b15d..bd5bff57ab 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -521,6 +521,23 @@
</section>
+<section><title>Ssh 4.9.1.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The idle_time timer was not cancelled when a channel was
+ opened within the timeout time on an empty connection
+ that have had channels previously.</p>
+ <p>
+ Own Id: OTP-17279</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.9.1.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 424bf05791..0b4211e1c3 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -86,8 +86,7 @@ trusted_cert_and_paths(Chain, CertDbHandle, CertDbRef, PartialChainHandler) ->
Result ->
Result
end
- end, Paths).
-
+ end, Paths).
%%--------------------------------------------------------------------
-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}) ->
{error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}.
@@ -533,7 +532,12 @@ handle_incomplete_chain([PeerCert| _] = Chain0, PartialChainHandler, Default, Ce
%% See if we have the certificates to rebuild it.
case certificate_chain(PeerCert, CertDbHandle, CertDbRef) of
{ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
- handle_partial_chain(lists:reverse(Chain), PartialChainHandler, CertDbHandle, CertDbRef);
+ case lists:prefix(Chain0, Chain) of
+ true ->
+ handle_partial_chain(lists:reverse(Chain), PartialChainHandler, CertDbHandle, CertDbRef);
+ false ->
+ Default
+ end;
_ ->
Default
end.
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index b737c41ba2..a82b682526 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -73,7 +73,9 @@
fake_root_no_intermediate_legacy/0,
fake_root_no_intermediate_legacy/1,
fake_intermediate_cert/0,
- fake_intermediate_cert/1
+ fake_intermediate_cert/1,
+ incompleat_chain_length/0,
+ incompleat_chain_length/1
]).
%% Apply export
@@ -124,7 +126,8 @@ basic_tests() ->
fake_root_no_intermediate,
fake_root_legacy,
fake_root_no_intermediate_legacy,
- fake_intermediate_cert
+ fake_intermediate_cert,
+ incompleat_chain_length
].
options_tests() ->
@@ -730,6 +733,74 @@ fake_intermediate_cert(Config) when is_list(Config) ->
ssl_test_lib:check_client_alert(Client1, bad_certificate).
+incompleat_chain_length() ->
+ [{doc,"Test that attempts to reconstruct incomplete chains does not make shorter incomplete chains"}].
+incompleat_chain_length(Config) when is_list(Config)->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ OtherROOT = public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {extensions, Ext}]),
+
+
+ #{client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := ServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => OtherROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
+ [{key, ssl_test_lib:hardcode_rsa_key(3)}]
+ ],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+
+ VerifyFun = {fun(_,{bad_cert, unknown_ca}, UserState) ->
+ %% accept this error to provoke the
+ %% building of an shorter incomplete chain
+ %% than the one recived
+ {valid, UserState};
+ (_,{extension, _} = Extension, #{ext := N} = UserState) ->
+ ct:pal("~p", [Extension]),
+ {unknown, UserState#{ext => N +1}};
+ (_, valid, #{intermediates := N} = UserState) ->
+ {valid, UserState#{intermediates => N +1}};
+ (_, valid_peer, #{intermediates := 2,
+ ext := 1} = UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ ct:pal("~p", [UserState]),
+ {error, {bad_cert, too_short_path}}
+ end, #{intermediates => 0,
+ ext => 0}},
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerConf}
+ ]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{verify, verify_peer}, {verify_fun, VerifyFun} | ClientConf]}]),
+ ssl_test_lib:check_result(Client, ok, Server, ok).
+
%%--------------------------------------------------------------------
%% callback functions ------------------------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index d15ffb9b40..354b65c9a3 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -5185,7 +5185,7 @@ function_type(Type) ->
%% Arguments = [erl_parse()]
%% Type = erl_parse()
--spec function_type('any_arity' | syntaxTree(), syntaxTree()) -> syntaxTree().
+-spec function_type('any_arity' | [syntaxTree()], syntaxTree()) -> syntaxTree().
function_type(Arguments, Return) ->
tree(function_type,
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 47373ca1fe..358bcf5043 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -141,6 +141,22 @@
</section>
+<section><title>Tools 3.3.1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>cover</c> would crash when compiling a module
+ having an exported function named <c>clauses</c>.</p>
+ <p>
+ Own Id: OTP-17162 Aux Id: GH-4549, PR-2997, PR-4555,
+ elixir-lang/elixir#10666 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/otp_versions.table b/otp_versions.table
index 0076c409bb..f6ccfd0612 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -20,6 +20,7 @@ OTP-23.0.3 : compiler-7.6.2 erts-11.0.3 # asn1-5.0.13 common_test-1.19 crypto-4.
OTP-23.0.2 : erts-11.0.2 megaco-3.19.1 # asn1-5.0.13 common_test-1.19 compiler-7.6.1 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
OTP-23.0.1 : compiler-7.6.1 erts-11.0.1 # asn1-5.0.13 common_test-1.19 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 megaco-3.19 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
OTP-23.0 : asn1-5.0.13 common_test-1.19 compiler-7.6 crypto-4.7 debugger-5.0 dialyzer-4.2 edoc-0.12 erl_docgen-1.0 erl_interface-4.0 erts-11.0 eunit-2.5 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 megaco-3.19 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tools-3.4 wx-1.9.1 xmerl-1.3.25 # diameter-2.2.3 eldap-1.2.8 et-1.6.4 ftp-1.0.4 reltool-0.8 tftp-1.0.2 :
+OTP-22.3.4.17 : erts-10.7.2.9 kernel-6.5.2.2 ssh-4.9.1.3 tools-3.3.1.1 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.3 crypto-4.6.5.2 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 megaco-3.18.8.3 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.4 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 wx-1.9 xmerl-1.3.24 :
OTP-22.3.4.16 : erts-10.7.2.8 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.3 crypto-4.6.5.2 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.3 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.4 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.4.15 : crypto-4.6.5.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.3 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.7 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.3 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.4 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.4.14 : compiler-7.5.4.3 erts-10.7.2.7 # asn1-5.0.12 common_test-1.18.2 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.3 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.4 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :