diff options
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 : |