diff options
325 files changed, 7736 insertions, 3513 deletions
diff --git a/.gitignore b/.gitignore index d40f49b56f..eb14036789 100644 --- a/.gitignore +++ b/.gitignore @@ -208,6 +208,7 @@ JAVADOC-GENERATED # common_test +/lib/common_test/doc/src/ct_property_test.xml /lib/common_test/doc/src/ct_slave.xml /lib/common_test/priv/install.sh diff --git a/OTP_VERSION b/OTP_VERSION index 6f1dcfcb03..9f09b58414 100644 --- a/OTP_VERSION +++ b/OTP_VERSION @@ -1 +1 @@ -17.3-rc0 +17.4-rc0 diff --git a/configure.in b/configure.in index 780e660f9d..008fa38632 100644 --- a/configure.in +++ b/configure.in @@ -262,6 +262,10 @@ AS_HELP_STRING([--with-ssl=PATH], [specify location of OpenSSL include and lib]) AS_HELP_STRING([--with-ssl], [use SSL (default)]) AS_HELP_STRING([--without-ssl], [don't use SSL])) +AC_ARG_WITH(ssl-incl, +AS_HELP_STRING([--with-ssl-incl=PATH], + [location of OpenSSL include dir, if different than specified by --with-ssl=PATH])) + AC_ARG_ENABLE(dynamic-ssl-lib, AS_HELP_STRING([--disable-dynamic-ssl-lib], [disable using dynamic openssl libraries])) @@ -412,7 +416,7 @@ AC_SUBST(NATIVE_LIBS_ENABLED) rm -f $ERL_TOP/lib/SKIP-APPLICATIONS for app in `cd lib && ls -d *`; do var=`eval echo \\$with_$app` - if test X${var} == Xno; then + if test X${var} = Xno; then echo "$app" >> $ERL_TOP/lib/SKIP-APPLICATIONS fi done diff --git a/erts/configure.in b/erts/configure.in index c8b96c50f0..9864d03cde 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -2109,6 +2109,17 @@ AC_CHECK_FUNCS([ieee_handler fpsetmask finite isnan isinf res_gethostbyname dlop flockfile fstat strlcpy strlcat setsid posix2time time2posix \ setlocale nl_langinfo poll mlockall]) +AC_MSG_CHECKING([for isfinite]) +AC_TRY_LINK([#include <math.h>], + [isfinite(0);], have_isfinite=yes, have_isfinite=no), + +if test $have_isfinite = yes; then + AC_DEFINE(HAVE_ISFINITE,[1], + [Define to 1 if you have the `isfinite' function.]) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi case X$erl_xcomp_posix_memalign in Xno) ;; @@ -3988,7 +3999,7 @@ dnl If set to --with-ssl=PATH we use that path as the prefix, i.e. we dnl use "PATH/include" and "PATH/lib". AC_SUBST(SSL_INCLUDE) -AC_SUBST(SSL_ROOT) +AC_SUBST(SSL_INCDIR) AC_SUBST(SSL_LIBDIR) AC_SUBST(SSL_CRYPTO_LIBNAME) AC_SUBST(SSL_SSL_LIBNAME) @@ -4082,6 +4093,15 @@ AS_HELP_STRING([--with-ssl=PATH], [specify location of OpenSSL include and lib]) AS_HELP_STRING([--with-ssl], [use SSL (default)]) AS_HELP_STRING([--without-ssl], [don't use SSL])) +AC_ARG_WITH(ssl-incl, +AS_HELP_STRING([--with-ssl-incl=PATH], [location of OpenSSL include dir, if different than specified by --with-ssl=PATH]), +[ +case X$with_ssl in + X | Xyes | Xno) AC_MSG_ERROR([--with-ssl-incl=PATH set without --with-ssl=PATH]);; +esac +], +[with_ssl_incl=$with_ssl]) #default + AC_ARG_ENABLE(dynamic-ssl-lib, AS_HELP_STRING([--disable-dynamic-ssl-lib], [disable using dynamic openssl libraries]), @@ -4196,7 +4216,7 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in dir="$erl_xcomp_sysroot$rdir" if test -f "$erl_xcomp_isysroot$rdir/include/openssl/opensslv.h"; then is_real_ssl=yes - SSL_ROOT="$dir" + SSL_INCDIR="$dir" if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then if test -f "$dir/lib/VC/libeay32.lib"; then SSL_RUNTIME_LIBDIR="$rdir/lib/VC" @@ -4326,8 +4346,8 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in # Trust OpenBSD to have everything the in the correct locations. ssl_found=yes ssl_linkable=yes - SSL_ROOT="$erl_xcomp_sysroot/usr" - AC_MSG_RESULT([$SSL_ROOT]) + SSL_INCDIR="$erl_xcomp_sysroot/usr" + AC_MSG_RESULT([$SSL_INCDIR]) SSL_RUNTIME_LIB="/usr/lib" SSL_LIB="$erl_xcomp_sysroot/usr/lib" SSL_BINDIR="/usr/sbin" @@ -4394,7 +4414,10 @@ dnl so it is - be adoptable if test ! -d "$with_ssl" ; then AC_MSG_ERROR(Invalid path to option --with-ssl=PATH) fi - SSL_ROOT="$with_ssl" + if test ! -d "$with_ssl_incl" ; then + AC_MSG_ERROR(Invalid path to option --with-ssl-incl=PATH) + fi + SSL_INCDIR="$with_ssl_incl" SSL_CRYPTO_LIBNAME=crypto SSL_SSL_LIBNAME=ssl if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes" && test -d "$with_ssl/lib/VC"; then @@ -4444,12 +4467,12 @@ dnl so it is - be adoptable elif test '!' -f ${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.so -a '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.dylib"; then SSL_STATIC_ONLY=yes fi - SSL_INCLUDE="-I$with_ssl/include" + SSL_INCLUDE="-I$with_ssl_incl/include" SSL_APP=ssl CRYPTO_APP=crypto SSH_APP=ssh if test "$cross_compiling" = "yes"; then - SSL_RUNTIME_LIBDIR=`echo "$SSL_LIBDIR" | sed -n "s|^$erl_xcomp_sysroot\(.*\)\$|\1|p"` + SSL_RUNTIME_LIBDIR=`echo "$SSL_LIBDIR" | sed -n "s|^$erl_xcomp_sysroot\(/*\)\(.*\)\$|/\2|p"` else SSL_RUNTIME_LIBDIR="$SSL_LIBDIR" fi @@ -4507,8 +4530,8 @@ if test "x$SSL_APP" != "x" ; then SSL_KRB5_INCLUDE= if test "x$ssl_krb5_enabled" = "xyes" ; then AC_MSG_CHECKING(for krb5.h in standard locations) - for dir in $extra_dir "$SSL_ROOT/include" "$SSL_ROOT/include/openssl" \ - "$SSL_ROOT/include/kerberos" \ + for dir in $extra_dir "$SSL_INCDIR/include" "$SSL_INCDIR/include/openssl" \ + "$SSL_INCDIR/include/kerberos" \ "$erl_xcomp_isysroot/cygdrive/c/kerberos/include" \ "$erl_xcomp_isysroot/usr/local/kerberos/include" \ "$erl_xcomp_isysroot/usr/kerberos/include" \ @@ -4805,7 +4828,7 @@ AH_BOTTOM([ #define HAVE_GETHRVTIME #endif -#ifndef HAVE_FINITE +#if !defined(HAVE_ISFINITE) && !defined(HAVE_FINITE) # if defined(HAVE_ISINF) && defined(HAVE_ISNAN) # define USE_ISINF_ISNAN # endif diff --git a/erts/doc/src/crash_dump.xml b/erts/doc/src/crash_dump.xml index d3de29b876..2b5fc877c3 100644 --- a/erts/doc/src/crash_dump.xml +++ b/erts/doc/src/crash_dump.xml @@ -115,8 +115,9 @@ sockets/pipes can be used simultaneously by Erlang (due to limitations in the Unix <c><![CDATA[select]]></c> call). The number of open regular files is not affected by this.</item> - <item>"Received SIGUSR1" - The SIGUSR1 signal was sent to the - Erlang machine (Unix only).</item> + <item>"Received SIGUSR1" - Sending the SIGUSR1 signal to a + Erlang machine (Unix only) forces a crash dump. This slogan reflects + that the Erlang machine crash-dumped due to receiving that signal.</item> <item>"Kernel pid terminated (<em>Who</em>) (<em>Exit-reason</em>)" - The kernel supervisor has detected a failure, usually that the <c><![CDATA[application_controller]]></c> diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl.xml index f856b9ab86..d11f6b0c6d 100644 --- a/erts/doc/src/erl.xml +++ b/erts/doc/src/erl.xml @@ -525,7 +525,8 @@ core dump and no crash dump if an internal error is detected.</p> <p>Calling <c>erlang:halt/1</c> with a string argument will still - produce a crash dump.</p> + produce a crash dump. On Unix systems, sending an emulator process + a SIGUSR1 signal will also force a crash dump.</p> </item> <tag><marker id="+e"><c><![CDATA[+e Number]]></c></marker></tag> <item> @@ -1141,6 +1142,23 @@ <p>For more information, see <seealso marker="erlang#system_info_cpu_topology">erlang:system_info(cpu_topology)</seealso>.</p> </item> + <tag><marker id="+secio"><c>+secio true|false</c></marker></tag> + <item> + <p>Enable or disable eager check I/O scheduling. The default + is currently <c>false</c>, but will most likely be changed + to <c>true</c> in OTP 18. The behaviour before this flag + was introduced corresponds to <c>+secio false</c>.</p> + <p>The flag effects when schedulers will check for I/O + operations possible to execute, and when such I/O operations + will execute. As the name of the parameter implies, + schedulers will be more eager to check for I/O when + <c>true</c> is passed. This however also implies that + execution of outstanding I/O operation will not be + prioritized to the same extent as when <c>false</c> is + passed.</p> + <p><seealso marker="erlang#system_info_eager_check_io"><c>erlang:system_info(eager_check_io)</c></seealso> + returns the value of this parameter used when starting the VM.</p> + </item> <tag><marker id="+sfwi"><c>+sfwi Interval</c></marker></tag> <item> <p>Set scheduler forced wakeup interval. All run queues will diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml index ad37813ac0..77fc906aca 100644 --- a/erts/doc/src/erl_driver.xml +++ b/erts/doc/src/erl_driver.xml @@ -546,6 +546,7 @@ typedef struct ErlDrvSysInfo { int scheduler_threads; int nif_major_version; int nif_minor_version; + int dirty_scheduler_support; } ErlDrvSysInfo; </code> @@ -610,6 +611,10 @@ typedef struct ErlDrvSysInfo { <tag><c>nif_minor_version</c></tag> <item>The value of <c>ERL_NIF_MINOR_VERSION</c> when the runtime system was compiled. </item> + <tag><c>dirty_scheduler_support</c></tag> + <item>A value <c>!= 0</c> if the runtime system has support for dirty scheduler threads; + otherwise <c>0</c>. + </item> </taglist> </item> <tag><marker id="ErlDrvBinary"/>ErlDrvBinary</tag> @@ -2028,7 +2033,8 @@ ERL_DRV_MAP int sz entry function is called. If <c>ready_async</c> is null in the driver entry, the <c>async_free</c> function is called instead.</p> - <p>The return value is a handle to the asynchronous task.</p> + <p>The return value is -1 if the <c>driver_async</c> call + fails.</p> <note> <p>As of erts version 5.5.4.3 the default stack size for threads in the async-thread pool is 16 kilowords, diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index 1d33b334bb..3de94be9ff 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -374,9 +374,8 @@ ok the dirty NIF API is available, native code can check to see if the C preprocessor macro <c>ERL_NIF_DIRTY_SCHEDULER_SUPPORT</c> is defined. Also, if the Erlang runtime was built without threading support, dirty schedulers are disabled. To check at runtime for the presence - of dirty scheduler threads, code can call the <seealso marker="#enif_have_dirty_schedulers"><c> - enif_have_dirty_schedulers()</c></seealso> API function, which returns true if dirty - scheduler threads are present, false otherwise.</p></note> + of dirty scheduler threads, code can use the <seealso marker="#enif_system_info"><c> + enif_system_info()</c></seealso> API function.</p></note> </item> </taglist> </section> @@ -807,22 +806,6 @@ typedef enum { and return true, or return false if <c>term</c> is not an unsigned integer or is outside the bounds of type <c>unsigned long</c>.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_have_dirty_schedulers()</nametext></name> - <fsummary>Runtime check for the presence of dirty scheduler threads</fsummary> - <desc> - <p>Check at runtime for the presence of dirty scheduler threads. If the emulator is - built with threading support, dirty scheduler threads are available and - <c>enif_have_dirty_schedulers()</c> returns true. If the emulator was built without - threading support, <c>enif_have_dirty_schedulers()</c> returns false.</p> - <p>If dirty scheduler threads are not available in the emulator, a call to - <c>enif_schedule_nif</c> with its <c>flags</c> argument set to indicate that the specified - NIF is to be executed on a dirty scheduler thread results in a <c>badarg</c> exception.</p> - <note><p>This function is available only when the emulator is configured with dirty - schedulers enabled. This feature is currently disabled by default. To determine whether - the dirty NIF API is available, native code can check to see if the C preprocessor macro - <c>ERL_NIF_DIRTY_SCHEDULER_SUPPORT</c> is defined.</p></note> - </desc> - </func> <func><name><ret>int</ret><nametext>enif_inspect_binary(ErlNifEnv* env, ERL_NIF_TERM bin_term, ErlNifBinary* bin)</nametext></name> <fsummary>Inspect the content of a binary</fsummary> <desc><p>Initialize the structure pointed to by <c>bin</c> with @@ -1261,7 +1244,9 @@ typedef enum { <p>The <c>flags</c> argument must be set to 0 for a regular NIF, or if the emulator was built the experimental dirty scheduler support enabled, <c>flags</c> can be set to either <c>ERL_NIF_DIRTY_JOB_CPU_BOUND</c> if the job is expected to be primarily CPU-bound, or <c>ERL_NIF_DIRTY_JOB_IO_BOUND</c> for jobs that will - be I/O-bound.</p> + be I/O-bound. If dirty scheduler threads are not available in the emulator, a try to schedule such a job + will result in a <c>badarg</c> exception.</p> + <p>The <c>argc</c> and <c>argv</c> arguments can either be the originals passed into the calling NIF, or they can be values created by the calling NIF.</p> <p>The calling NIF must use the return value of <c>enif_schedule_nif</c> as its own return value.</p> diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 84168397f6..111756407f 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -5775,6 +5775,7 @@ ok <name name="system_info" arity="1" clause_i="52"/> <name name="system_info" arity="1" clause_i="53"/> <name name="system_info" arity="1" clause_i="54"/> + <name name="system_info" arity="1" clause_i="55"/> <fsummary>Information about the system</fsummary> <desc> <p>Returns various information about the current system @@ -5970,6 +5971,16 @@ ok The return value will always be <c>false</c> since the elib_malloc allocator has been removed.</p> </item> + <tag><marker id="system_info_eager_check_io"><c>eager_check_io</c></marker></tag> + <item> + <p> + Returns the value of the <c>erl</c> + <seealso marker="erl#+secio">+secio</seealso> command line + flag which is either <c>true</c> or <c>false</c>. See the + documentation of the command line flag for information about + the different values. + </p> + </item> <tag><c>ets_limit</c></tag> <item> <p>Returns the maximum number of ETS tables allowed. This limit diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml index 5c4bb3ed25..743369951f 100644 --- a/erts/doc/src/notes.xml +++ b/erts/doc/src/notes.xml @@ -30,6 +30,176 @@ </header> <p>This document describes the changes made to the ERTS application.</p> +<section><title>Erts 6.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + General documentation updates.</p> + <p> + Own Id: OTP-12052</p> + </item> + <item> + <p>A bug in the VM code implementing sending of signals + to ports could cause the receiving port queue to remain + in a busy state forever. When this state had been + reached, processes sending command signals to the port + either got suspended forever, or, if the <c>nosuspend</c> + feature was used, always failed to send to the port. This + bug was introduced in ERTS version 5.10.</p> + <p>In order for this bug to be triggered on a port, one + had to at least once utilize the <c>nosuspend</c> + functionality when passing a signal to the port. This by + either calling</p> <list> <item> <seealso + marker="erlang#port_command/3"><c>port_command(Port, + Data, [nosuspend | Options])</c></seealso>, </item> + <item> <seealso + marker="erlang#send/3"><c>erlang:send(Port, {PortOwner, + {command, Data}}, [nosuspend | Options])</c></seealso>, + </item> <item> <seealso + marker="erlang#send_nosuspend/2"><c>erlang:send_nosuspend(Port, + {PortOwner, {command, Data}})</c></seealso>, or </item> + <item> <seealso + marker="erlang#send_nosuspend/3"><c>erlang:send_nosuspend(Port, + {PortOwner, {command, Data}}, Options)</c></seealso>. + </item> </list> + <p>Thanks Vasily Demidenok for reporting the issue, and + Sergey Kudryashov for providing a testcase.</p> + <p> + Own Id: OTP-12082 Aux Id: OTP-10336 </p> + </item> + <item> + <p> + Fix size overflow bug at memory allocation. A memory + allocation call, with an insane size close to the entire + address space, could return successfully as if it had + allocated just a few bytes. (Thanks to Don A. Bailey for + reporting)</p> + <p> + Own Id: OTP-12091</p> + </item> + <item> + <p> + Fix various issues where negating a signed integer would + trigger undefined behaviour. This fixes issues in the + enif_make_int64 interface and some edge cases inside the + erlang runtime system.</p> + <p> + Own Id: OTP-12097</p> + </item> + <item> + <p> + The documentation erroneously listed the <seealso + marker="erl#+swct"><c>+swct</c></seealso> command line + argument under <c>+sws</c>.</p> + <p> + Own Id: OTP-12102 Aux Id: OTP-10994 </p> + </item> + <item> + <p> + Profiling messages could be delivered out of order when + profiling on <c>runnable_procs</c> and/or + <c>runnable_ports</c> using <seealso + marker="erlang#system_profile/2"><c>erlang:system_profile/2</c></seealso>. + This bug was introduced in ERTS version 5.10.</p> + <p> + Own Id: OTP-12105 Aux Id: OTP-10336 </p> + </item> + <item> + <p> + Various logging fixes, including: Add run queue index to + the process dump in crash dumps.<br/> Add thread index to + enomem slogan when crashing.<br/> Remove error logger + message for sending messages to old instances of the same + node.</p> + <p> + Own Id: OTP-12115</p> + </item> + <item> + <p> + Fix compiler warnings reported by LLVM</p> + <p> + Own Id: OTP-12138</p> + </item> + <item> + <p> + Correct conversion of <c>MIN_SMALL</c> by + <c>list_to_integer/1</c> and <c>binary_to_integer/1</c>. + The bug produced an unnormalized bignum which can cause + strange behavior such as comparing different to a correct + <c>MIN_SMALL</c> integer. The value <c>MIN_SMALL</c> is + <c>-(1 bsl 27) = -134217728</c> on a 32-bit VM and <c>-(1 + bsl 59) = -576460752303423488</c> on a 64-bit VM. (Thanks + to Jesper Louis Andersen, Mikael Pettersson and Anthony + Ramine for report, patch and optimization suggestion)</p> + <p> + Own Id: OTP-12140</p> + </item> + <item> + <p> + Fix bug in <c>term_to_binary</c> that reallocates binary + with inconsistent size information. Bug has never been + confirmed to be the cause of any faulty behavior.</p> + <p> + Own Id: OTP-12141</p> + </item> + <item> + <p> + Real_path method used while prim loading archive files + was not taking into account the fact that windows + directory symlinks can be across different drives.</p> + <p> + Own Id: OTP-12155</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add log2 histogram to lcnt for lock wait time</p> + <p> + Own Id: OTP-12059</p> + </item> + <item> + <p> + Introduced <seealso + marker="erl_nif#enif_schedule_nif"><c>enif_schedule_nif()</c></seealso> + to the NIF API.</p> + <p> + The <c>enif_schedule_nif()</c> function allows a + long-running NIF to be broken into separate NIF + invocations without the help of a wrapper function + written in Erlang. The NIF first executes part of the + long-running task, then calls <c>enif_schedule_nif()</c> + to schedule a NIF for later execution to continue the + task. Any number of NIFs can be scheduled in this manner, + one after another. Since the emulator regains control + between invocations, this helps avoid problems caused by + native code tying up scheduler threads for too long.</p> + <p> + The <c>enif_schedule_nif()</c> function also replaces the + <c>enif_schedule_dirty_nif()</c> in the experimental + dirty NIF API. Note that the only incompatible changes + made are in the experimental dirty NIF API.</p> + <p> + See the <seealso marker="erl_nif">NIF + documentation</seealso> for more information.</p> + <p> + Thanks to Steve Vinoski.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-12128</p> + </item> + </list> + </section> + +</section> + <section><title>Erts 6.1.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/erts/emulator/beam/beam_ranges.c b/erts/emulator/beam/beam_ranges.c index 0f2d5d0c2a..cb6470638f 100644 --- a/erts/emulator/beam/beam_ranges.c +++ b/erts/emulator/beam/beam_ranges.c @@ -282,7 +282,7 @@ find_range(BeamInstr* pc) while (low < high) { if (pc < mid->start) { high = mid; - } else if (pc > RANGE_END(mid)) { + } else if (pc >= RANGE_END(mid)) { low = mid + 1; } else { erts_smp_atomic_set_nob(&r[active].mid, (erts_aint_t) mid); diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types index 37354b7f8d..21434eb117 100644 --- a/erts/emulator/beam/erl_alloc.types +++ b/erts/emulator/beam/erl_alloc.types @@ -267,7 +267,6 @@ type CODE_IX_LOCK_Q SHORT_LIVED SYSTEM code_ix_lock_q type PROC_INTERVAL LONG_LIVED SYSTEM process_interval type BUSY_CALLER_TAB SHORT_LIVED SYSTEM busy_caller_table type BUSY_CALLER SHORT_LIVED SYSTEM busy_caller -type PORT_DATA_HEAP STANDARD SYSTEM port_data_heap type PROC_SYS_TSK SHORT_LIVED PROCESSES proc_sys_task type PROC_SYS_TSK_QS SHORT_LIVED PROCESSES proc_sys_task_queues @@ -364,6 +363,7 @@ type NLINK_SH STANDARD_LOW PROCESSES nlink_sh type AINFO_REQ STANDARD_LOW SYSTEM alloc_info_request type SCHED_WTIME_REQ STANDARD_LOW SYSTEM sched_wall_time_request type GC_INFO_REQ STANDARD_LOW SYSTEM gc_info_request +type PORT_DATA_HEAP STANDARD_LOW SYSTEM port_data_heap +else # "fullword" @@ -383,6 +383,7 @@ type NLINK_SH FIXED_SIZE PROCESSES nlink_sh type AINFO_REQ SHORT_LIVED SYSTEM alloc_info_request type SCHED_WTIME_REQ SHORT_LIVED SYSTEM sched_wall_time_request type GC_INFO_REQ SHORT_LIVED SYSTEM gc_info_request +type PORT_DATA_HEAP STANDARD SYSTEM port_data_heap +endif @@ -397,6 +398,7 @@ type DRV_EV_STATE LONG_LIVED SYSTEM driver_event_state type DRV_EV_D_STATE FIXED_SIZE SYSTEM driver_event_data_state type DRV_SEL_D_STATE FIXED_SIZE SYSTEM driver_select_data_state type FD_LIST SHORT_LIVED SYSTEM fd_list +type ACTIVE_FD_ARR SHORT_LIVED SYSTEM active_fd_array type POLLSET LONG_LIVED SYSTEM pollset type POLLSET_UPDREQ SHORT_LIVED SYSTEM pollset_update_req type POLL_FDS LONG_LIVED SYSTEM poll_fds diff --git a/erts/emulator/beam/erl_alloc_util.c b/erts/emulator/beam/erl_alloc_util.c index a4e164bf51..55052430e1 100644 --- a/erts/emulator/beam/erl_alloc_util.c +++ b/erts/emulator/beam/erl_alloc_util.c @@ -1775,6 +1775,18 @@ handle_delayed_dealloc(Allctr_t *allctr, * data has been overwritten by the queue. */ Carrier_t *crr = FIRST_BLK_TO_MBC(allctr, blk); + + /* Restore word overwritten by the dd-queue as it will be read + * if this carrier is pulled from dc_list by cpool_fetch() + */ + ERTS_ALC_CPOOL_ASSERT(FBLK_TO_MBC(blk) != crr); + ERTS_ALC_CPOOL_ASSERT(sizeof(ErtsAllctrDDBlock_t) == sizeof(void*)); +#ifdef MBC_ABLK_OFFSET_BITS + blk->u.carrier = crr; +#else + blk->carrier = crr; +#endif + ERTS_ALC_CPOOL_ASSERT(ERTS_ALC_IS_CPOOL_ENABLED(allctr)); ERTS_ALC_CPOOL_ASSERT(allctr == crr->cpool.orig_allctr); ERTS_ALC_CPOOL_ASSERT(((erts_aint_t) allctr) diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c index 6efe9d9550..61e4469600 100644 --- a/erts/emulator/beam/erl_bif_info.c +++ b/erts/emulator/beam/erl_bif_info.c @@ -2696,6 +2696,9 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1) ? am_disabled : am_enabled); } + else if (ERTS_IS_ATOM_STR("eager_check_io",BIF_ARG_1)) { + BIF_RET(erts_eager_check_io ? am_true : am_false); + } BIF_ERROR(BIF_P, BADARG); } @@ -3304,17 +3307,38 @@ BIF_RETTYPE erts_debug_get_internal_state_1(BIF_ALIST_1) BIF_RET(make_small((Uint) words)); } else if (ERTS_IS_ATOM_STR("check_io_debug", BIF_ARG_1)) { - /* Used by (emulator) */ - int res; + /* Used by driver_SUITE (emulator) */ + Uint sz, *szp; + Eterm res, *hp, **hpp; + int no_errors; + ErtsCheckIoDebugInfo ciodi = {0}; #ifdef HAVE_ERTS_CHECK_IO_DEBUG erts_smp_proc_unlock(BIF_P,ERTS_PROC_LOCK_MAIN); - res = erts_check_io_debug(); + no_errors = erts_check_io_debug(&ciodi); erts_smp_proc_lock(BIF_P,ERTS_PROC_LOCK_MAIN); #else - res = 0; + no_errors = 0; #endif - ASSERT(res >= 0); - BIF_RET(erts_make_integer((Uint) res, BIF_P)); + sz = 0; + szp = &sz; + hpp = NULL; + while (1) { + res = erts_bld_tuple(hpp, szp, 4, + erts_bld_uint(hpp, szp, + (Uint) no_errors), + erts_bld_uint(hpp, szp, + (Uint) ciodi.no_used_fds), + erts_bld_uint(hpp, szp, + (Uint) ciodi.no_driver_select_structs), + erts_bld_uint(hpp, szp, + (Uint) ciodi.no_driver_event_structs)); + if (hpp) + break; + hp = HAlloc(BIF_P, sz); + szp = NULL; + hpp = &hp; + } + BIF_RET(res); } else if (ERTS_IS_ATOM_STR("process_info_args", BIF_ARG_1)) { /* Used by process_SUITE (emulator) */ diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c index afb33c1cdb..64bd598ba6 100644 --- a/erts/emulator/beam/erl_bif_port.c +++ b/erts/emulator/beam/erl_bif_port.c @@ -493,8 +493,8 @@ void erts_cleanup_port_data(Port *prt) { ASSERT(erts_atomic32_read_nob(&prt->state) & ERTS_PORT_SFLGS_INVALID_LOOKUP); - cleanup_old_port_data(erts_smp_atomic_read_nob(&prt->data)); - erts_smp_atomic_set_nob(&prt->data, (erts_aint_t) THE_NON_VALUE); + cleanup_old_port_data(erts_smp_atomic_xchg_nob(&prt->data, + (erts_aint_t) NULL)); } Uint @@ -554,6 +554,7 @@ BIF_RETTYPE port_set_data_2(BIF_ALIST_2) hp = &pdhp->heap[0]; pdhp->off_heap.first = NULL; pdhp->off_heap.overhead = 0; + pdhp->hsize = hsize; pdhp->data = copy_struct(BIF_ARG_2, hsize, &hp, &pdhp->off_heap); data = (erts_aint_t) pdhp; ASSERT((data & 0x3) == 0); @@ -561,8 +562,14 @@ BIF_RETTYPE port_set_data_2(BIF_ALIST_2) data = erts_smp_atomic_xchg_wb(&prt->data, data); + if (data == (erts_aint_t)NULL) { + /* Port terminated by racing thread */ + data = erts_smp_atomic_xchg_wb(&prt->data, data); + ASSERT(data != (erts_aint_t)NULL); + cleanup_old_port_data(data); + BIF_ERROR(BIF_P, BADARG); + } cleanup_old_port_data(data); - BIF_RET(am_true); } @@ -581,6 +588,8 @@ BIF_RETTYPE port_get_data_1(BIF_ALIST_1) BIF_ERROR(BIF_P, BADARG); data = erts_smp_atomic_read_ddrb(&prt->data); + if (data == (erts_aint_t)NULL) + BIF_ERROR(BIF_P, BADARG); /* Port terminated by racing thread */ if ((data & 0x3) != 0) { res = (Eterm) (UWord) data; diff --git a/erts/emulator/beam/erl_driver.h b/erts/emulator/beam/erl_driver.h index 5ced8c5ca0..f9938fc66c 100644 --- a/erts/emulator/beam/erl_driver.h +++ b/erts/emulator/beam/erl_driver.h @@ -133,7 +133,7 @@ typedef struct { #define ERL_DRV_EXTENDED_MARKER (0xfeeeeeed) #define ERL_DRV_EXTENDED_MAJOR_VERSION 3 -#define ERL_DRV_EXTENDED_MINOR_VERSION 0 +#define ERL_DRV_EXTENDED_MINOR_VERSION 1 /* * The emulator will refuse to load a driver with a major version diff --git a/erts/emulator/beam/erl_drv_nif.h b/erts/emulator/beam/erl_drv_nif.h index 3f829ea7ea..4e8c6dc68b 100644 --- a/erts/emulator/beam/erl_drv_nif.h +++ b/erts/emulator/beam/erl_drv_nif.h @@ -35,6 +35,7 @@ typedef struct { int scheduler_threads; int nif_major_version; int nif_minor_version; + int dirty_scheduler_support; } ErlDrvSysInfo; typedef struct { diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c index 88c4006934..61f8385efc 100644 --- a/erts/emulator/beam/erl_init.c +++ b/erts/emulator/beam/erl_init.c @@ -548,6 +548,8 @@ void erts_usage(void) erts_fprintf(stderr, " see the erl(1) documentation for more info.\n"); erts_fprintf(stderr, "-sct cput set cpu topology,\n"); erts_fprintf(stderr, " see the erl(1) documentation for more info.\n"); + erts_fprintf(stderr, "-secio bool enable/disable eager check I/O scheduling,\n"); + erts_fprintf(stderr, " see the erl(1) documentation for more info.\n"); #if ERTS_HAVE_SCHED_UTIL_BALANCING_SUPPORT_OPT erts_fprintf(stderr, "-sub bool enable/disable scheduler utilization balancing,\n"); #else @@ -1674,6 +1676,22 @@ erl_start(int argc, char **argv) erts_usage(); } } + else if (has_prefix("ecio", sub_param)) { + arg = get_arg(sub_param+4, argv[i+1], &i); +#ifndef __OSE__ + if (sys_strcmp("true", arg) == 0) + erts_eager_check_io = 1; + else +#endif + if (sys_strcmp("false", arg) == 0) + erts_eager_check_io = 0; + else { + erts_fprintf(stderr, + "bad schedule eager check I/O value '%s'\n", + arg); + erts_usage(); + } + } else if (has_prefix("pp", sub_param)) { arg = get_arg(sub_param+2, argv[i+1], &i); if (sys_strcmp(arg, "true") == 0) diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c index 44914d3681..ede5f335dc 100644 --- a/erts/emulator/beam/erl_nif.c +++ b/erts/emulator/beam/erl_nif.c @@ -1901,16 +1901,6 @@ enif_is_on_dirty_scheduler(ErlNifEnv* env) return ERTS_SCHEDULER_IS_DIRTY(env->proc->scheduler_data); } -int -enif_have_dirty_schedulers() -{ -#ifdef USE_THREADS - return 1; -#else - return 0; -#endif -} - #endif /* ERL_NIF_DIRTY_SCHEDULER_SUPPORT */ /* Maps */ diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h index 226fc199a1..849024453c 100644 --- a/erts/emulator/beam/erl_nif.h +++ b/erts/emulator/beam/erl_nif.h @@ -241,21 +241,10 @@ extern TWinDynNifCallbacks WinDynNifCallbacks; # else # define ERL_NIF_INIT_DECL(MODNAME) __declspec(dllexport) ErlNifEntry* nif_init(TWinDynNifCallbacks* callbacks) # endif -# ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT -# define ERL_NIF_INIT_BODY do { \ - memcpy(&WinDynNifCallbacks,callbacks,sizeof(TWinDynNifCallbacks)); \ - entry.options = ERL_NIF_DIRTY_NIF_OPTION; \ - } while(0) -# else -# define ERL_NIF_INIT_BODY memcpy(&WinDynNifCallbacks,callbacks,sizeof(TWinDynNifCallbacks)) -# endif +# define ERL_NIF_INIT_BODY memcpy(&WinDynNifCallbacks,callbacks,sizeof(TWinDynNifCallbacks)) #else # define ERL_NIF_INIT_GLOB -# ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT -# define ERL_NIF_INIT_BODY entry.options = ERL_NIF_DIRTY_NIF_OPTION -# else -# define ERL_NIF_INIT_BODY -# endif +# define ERL_NIF_INIT_BODY # ifdef STATIC_ERLANG_NIF # define ERL_NIF_INIT_DECL(MODNAME) ErlNifEntry* MODNAME ## _nif_init(void) # else @@ -263,6 +252,11 @@ extern TWinDynNifCallbacks WinDynNifCallbacks; # endif #endif +#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT +# define ERL_NIF_ENTRY_OPTIONS ERL_NIF_DIRTY_NIF_OPTION +#else +# define ERL_NIF_ENTRY_OPTIONS 0 +#endif #ifdef __cplusplus } @@ -288,7 +282,8 @@ ERL_NIF_INIT_DECL(NAME) \ sizeof(FUNCS) / sizeof(*FUNCS), \ FUNCS, \ LOAD, RELOAD, UPGRADE, UNLOAD, \ - ERL_NIF_VM_VARIANT \ + ERL_NIF_VM_VARIANT, \ + ERL_NIF_ENTRY_OPTIONS \ }; \ ERL_NIF_INIT_BODY; \ return &entry; \ diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h index be39816a64..630cefae93 100644 --- a/erts/emulator/beam/erl_nif_api_funcs.h +++ b/erts/emulator/beam/erl_nif_api_funcs.h @@ -22,7 +22,7 @@ #endif /* -** WARNING: add new ERL_NIF_API_FUNC_DECL entries at the bottom of the list +** WARNING: Add new ERL_NIF_API_FUNC_DECL entries at the bottom of the list ** to keep compatibility on Windows!!! ** ** And don't forget to increase ERL_NIF_MINOR_VERSION in erl_nif.h @@ -141,12 +141,6 @@ ERL_NIF_API_FUNC_DECL(int,enif_is_number,(ErlNifEnv*, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(void*,enif_dlopen,(const char* lib, void (*err_handler)(void*,const char*), void* err_arg)); ERL_NIF_API_FUNC_DECL(void*,enif_dlsym,(void* handle, const char* symbol, void (*err_handler)(void*,const char*), void* err_arg)); ERL_NIF_API_FUNC_DECL(int,enif_consume_timeslice,(ErlNifEnv*, int percent)); -ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_schedule_nif,(ErlNifEnv*,const char*,int,ERL_NIF_TERM (*)(ErlNifEnv*,int,const ERL_NIF_TERM[]),int,const ERL_NIF_TERM[])); -#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT -ERL_NIF_API_FUNC_DECL(int,enif_is_on_dirty_scheduler,(ErlNifEnv*)); -ERL_NIF_API_FUNC_DECL(int,enif_have_dirty_schedulers,(void)); -#endif - ERL_NIF_API_FUNC_DECL(int, enif_is_map, (ErlNifEnv* env, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(int, enif_get_map_size, (ErlNifEnv* env, ERL_NIF_TERM term, size_t *size)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM, enif_make_new_map, (ErlNifEnv* env)); @@ -161,12 +155,22 @@ ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_is_tail, (ErlNifEnv *env, ErlNifMap ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_next, (ErlNifEnv *env, ErlNifMapIterator *iter)); ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_prev, (ErlNifEnv *env, ErlNifMapIterator *iter)); ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_get_pair, (ErlNifEnv *env, ErlNifMapIterator *iter, ERL_NIF_TERM *key, ERL_NIF_TERM *value)); - +ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_schedule_nif,(ErlNifEnv*,const char*,int,ERL_NIF_TERM (*)(ErlNifEnv*,int,const ERL_NIF_TERM[]),int,const ERL_NIF_TERM[])); /* -** Add new entries here to keep compatibility on Windows!!! +** ADD NEW ENTRIES HERE (before this comment) !!! */ + + +/* + * Conditional EXPERIMENTAL stuff always last. + * Must be moved up and made unconditional to support binary backward + * compatibility on Windows. + */ +#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT +ERL_NIF_API_FUNC_DECL(int,enif_is_on_dirty_scheduler,(ErlNifEnv*)); #endif +#endif /* ERL_NIF_API_FUNC_DECL */ /* ** Please keep the ERL_NIF_API_FUNC_MACRO list below in the same order @@ -280,19 +284,12 @@ ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_get_pair, (ErlNifEnv *env, ErlNifMa # define enif_make_int64 ERL_NIF_API_FUNC_MACRO(enif_make_int64) # define enif_make_uint64 ERL_NIF_API_FUNC_MACRO(enif_make_uint64) #endif - # define enif_is_exception ERL_NIF_API_FUNC_MACRO(enif_is_exception) # define enif_make_reverse_list ERL_NIF_API_FUNC_MACRO(enif_make_reverse_list) # define enif_is_number ERL_NIF_API_FUNC_MACRO(enif_is_number) # define enif_dlopen ERL_NIF_API_FUNC_MACRO(enif_dlopen) # define enif_dlsym ERL_NIF_API_FUNC_MACRO(enif_dlsym) # define enif_consume_timeslice ERL_NIF_API_FUNC_MACRO(enif_consume_timeslice) -# define enif_schedule_nif ERL_NIF_API_FUNC_MACRO(enif_schedule_nif) -#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT -# define enif_is_on_dirty_scheduler ERL_NIF_API_FUNC_MACRO(enif_is_on_dirty_scheduler) -# define enif_have_dirty_schedulers ERL_NIF_API_FUNC_MACRO(enif_have_dirty_schedulers) -#endif - # define enif_is_map ERL_NIF_API_FUNC_MACRO(enif_is_map) # define enif_get_map_size ERL_NIF_API_FUNC_MACRO(enif_get_map_size) # define enif_make_new_map ERL_NIF_API_FUNC_MACRO(enif_make_new_map) @@ -307,11 +304,21 @@ ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_get_pair, (ErlNifEnv *env, ErlNifMa # define enif_map_iterator_next ERL_NIF_API_FUNC_MACRO(enif_map_iterator_next) # define enif_map_iterator_prev ERL_NIF_API_FUNC_MACRO(enif_map_iterator_prev) # define enif_map_iterator_get_pair ERL_NIF_API_FUNC_MACRO(enif_map_iterator_get_pair) +# define enif_schedule_nif ERL_NIF_API_FUNC_MACRO(enif_schedule_nif) /* -** Add new entries here +** ADD NEW ENTRIES HERE (before this comment) */ + +/* + * Conditional EXPERIMENTAL stuff always last + * Must be moved up and made unconditional to support binary backward + * compatibility on Windows. + */ +#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT +# define enif_is_on_dirty_scheduler ERL_NIF_API_FUNC_MACRO(enif_is_on_dirty_scheduler) #endif +#endif /* ERL_NIF_API_FUNC_MACRO */ #if defined(__GNUC__) && !(defined(__WIN32__) || defined(_WIN32) || defined(_WIN32_)) diff --git a/erts/emulator/beam/erl_port_task.c b/erts/emulator/beam/erl_port_task.c index 682f6f8f4b..2aa0a27197 100644 --- a/erts/emulator/beam/erl_port_task.c +++ b/erts/emulator/beam/erl_port_task.c @@ -32,6 +32,7 @@ #include "global.h" #include "erl_port_task.h" #include "dist.h" +#include "erl_check_io.h" #include "dtrace-wrapper.h" #include <stdarg.h> @@ -550,6 +551,16 @@ reset_handle(ErtsPortTask *ptp) } static ERTS_INLINE void +reset_executed_io_task_handle(ErtsPortTask *ptp) +{ + if (ptp->u.alive.handle) { + ASSERT(ptp == handle2task(ptp->u.alive.handle)); + erts_io_notify_port_task_executed(ptp->u.alive.handle); + reset_port_task_handle(ptp->u.alive.handle); + } +} + +static ERTS_INLINE void set_handle(ErtsPortTask *ptp, ErtsPortTaskHandle *pthp) { ptp->u.alive.handle = pthp; @@ -1396,10 +1407,7 @@ erts_port_task_schedule(Eterm id, erts_aint32_t act, add_flags; unsigned int prof_runnable_ports; - if (pthp && erts_port_task_is_scheduled(pthp)) { - ASSERT(0); - erts_port_task_abort(pthp); - } + ERTS_LC_ASSERT(!pthp || !erts_port_task_is_scheduled(pthp)); ASSERT(is_internal_port(id)); @@ -1699,8 +1707,6 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp) goto aborted_port_task; } - reset_handle(ptp); - if (erts_system_monitor_long_schedule != 0) { start_time = erts_timestamp_millis(); } @@ -1711,6 +1717,7 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp) switch (ptp->type) { case ERTS_PORT_TASK_TIMEOUT: + reset_handle(ptp); reds = ERTS_PORT_REDS_TIMEOUT; if (!(state & ERTS_PORT_SFLGS_DEAD)) { DTRACE_DRIVER(driver_timeout, pp); @@ -1725,6 +1732,7 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp) for input and output */ (*pp->drv_ptr->ready_input)((ErlDrvData) pp->drv_data, ptp->u.alive.td.io.event); + reset_executed_io_task_handle(ptp); io_tasks_executed++; break; case ERTS_PORT_TASK_OUTPUT: @@ -1733,6 +1741,7 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp) DTRACE_DRIVER(driver_ready_output, pp); (*pp->drv_ptr->ready_output)((ErlDrvData) pp->drv_data, ptp->u.alive.td.io.event); + reset_executed_io_task_handle(ptp); io_tasks_executed++; break; case ERTS_PORT_TASK_EVENT: @@ -1742,10 +1751,12 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp) (*pp->drv_ptr->event)((ErlDrvData) pp->drv_data, ptp->u.alive.td.io.event, ptp->u.alive.td.io.event_data); + reset_executed_io_task_handle(ptp); io_tasks_executed++; break; case ERTS_PORT_TASK_PROC_SIG: { ErtsProc2PortSigData *sigdp = &ptp->u.alive.td.psig.data; + reset_handle(ptp); ASSERT((state & ERTS_PORT_SFLGS_DEAD) == 0); if (!pp->sched.taskq.bpq) reds = ptp->u.alive.td.psig.callback(pp, @@ -1763,6 +1774,7 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp) break; } case ERTS_PORT_TASK_DIST_CMD: + reset_handle(ptp); reds = erts_dist_command(pp, CONTEXT_REDS - pp->reds); break; default: diff --git a/erts/emulator/beam/erl_port_task.h b/erts/emulator/beam/erl_port_task.h index 9ef0cfcedc..406cd3c492 100644 --- a/erts/emulator/beam/erl_port_task.h +++ b/erts/emulator/beam/erl_port_task.h @@ -156,7 +156,7 @@ erts_port_task_handle_init(ErtsPortTaskHandle *pthp) ERTS_GLB_INLINE int erts_port_task_is_scheduled(ErtsPortTaskHandle *pthp) { - return ((void *) erts_smp_atomic_read_nob(pthp)) != NULL; + return ((void *) erts_smp_atomic_read_acqb(pthp)) != NULL; } ERTS_GLB_INLINE void erts_port_task_pre_init_sched(ErtsPortTaskSched *ptsp, diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 20a88ec581..e5bb1203c8 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -148,6 +148,12 @@ extern BeamInstr beam_apply[]; extern BeamInstr beam_exit[]; extern BeamInstr beam_continue_exit[]; +#ifdef __OSE__ +/* Eager check I/O not supported on OSE yet. */ +int erts_eager_check_io = 0; +#else +int erts_eager_check_io = 0; +#endif int erts_sched_compact_load; int erts_sched_balance_util = 0; Uint erts_no_schedulers; @@ -2381,29 +2387,47 @@ try_set_sys_scheduling(void) #endif static ERTS_INLINE int -prepare_for_sys_schedule(ErtsSchedulerData *esdp) +prepare_for_sys_schedule(ErtsSchedulerData *esdp, int non_blocking) { + if (non_blocking && erts_eager_check_io) { #ifdef ERTS_SMP - while (!erts_port_task_have_outstanding_io_tasks() - && try_set_sys_scheduling()) { #ifdef ERTS_SCHED_ONLY_POLL_SCHED_1 - if (esdp->no != 1) { - /* If we are not scheduler 1 and ERTS_SCHED_ONLY_POLL_SCHED_1 is used - then we make sure to wake scheduler 1 */ - ErtsRunQueue *rq = ERTS_RUNQ_IX(0); - clear_sys_scheduling(); - wake_scheduler(rq); - return 0; - } + if (esdp->no != 1) { + /* If we are not scheduler 1 and ERTS_SCHED_ONLY_POLL_SCHED_1 is used + then we make sure to wake scheduler 1 */ + ErtsRunQueue *rq = ERTS_RUNQ_IX(0); + wake_scheduler(rq); + return 0; + } #endif - if (!erts_port_task_have_outstanding_io_tasks()) + return try_set_sys_scheduling(); +#else return 1; - clear_sys_scheduling(); +#endif } - return 0; + else { +#ifdef ERTS_SMP + while (!erts_port_task_have_outstanding_io_tasks() + && try_set_sys_scheduling()) { +#ifdef ERTS_SCHED_ONLY_POLL_SCHED_1 + if (esdp->no != 1) { + /* If we are not scheduler 1 and ERTS_SCHED_ONLY_POLL_SCHED_1 is used + then we make sure to wake scheduler 1 */ + ErtsRunQueue *rq = ERTS_RUNQ_IX(0); + clear_sys_scheduling(); + wake_scheduler(rq); + return 0; + } +#endif + if (!erts_port_task_have_outstanding_io_tasks()) + return 1; + clear_sys_scheduling(); + } + return 0; #else - return !erts_port_task_have_outstanding_io_tasks(); + return !erts_port_task_have_outstanding_io_tasks(); #endif + } } #ifdef ERTS_SMP @@ -2780,7 +2804,7 @@ scheduler_wait(int *fcalls, ErtsSchedulerData *esdp, ErtsRunQueue *rq) * be waiting in erl_sys_schedule() */ - if (ERTS_SCHEDULER_IS_DIRTY(esdp) || !prepare_for_sys_schedule(esdp)) { + if (ERTS_SCHEDULER_IS_DIRTY(esdp) || !prepare_for_sys_schedule(esdp, 0)) { sched_waiting(esdp->no, rq); @@ -2944,7 +2968,7 @@ scheduler_wait(int *fcalls, ErtsSchedulerData *esdp, ErtsRunQueue *rq) * Got to check that we still got I/O tasks; otherwise * we have to continue checking for I/O... */ - if (!prepare_for_sys_schedule(esdp)) { + if (!prepare_for_sys_schedule(esdp, 0)) { spincount *= ERTS_SCHED_TSE_SLEEP_SPINCOUNT_FACT; goto tse_wait; } @@ -2966,7 +2990,7 @@ scheduler_wait(int *fcalls, ErtsSchedulerData *esdp, ErtsRunQueue *rq) * Got to check that we still got I/O tasks; otherwise * we have to wait in erl_sys_schedule() after all... */ - if (!prepare_for_sys_schedule(esdp)) { + if (!prepare_for_sys_schedule(esdp, 0)) { /* * Not allowed to wait in erl_sys_schedule; * do tse wait instead... @@ -9200,7 +9224,7 @@ Process *schedule(Process *p, int calls) } else if (!ERTS_SCHEDULER_IS_DIRTY(esdp) && (fcalls > input_reductions && - prepare_for_sys_schedule(esdp))) { + prepare_for_sys_schedule(esdp, !0))) { /* * Schedule system-level activities. */ @@ -9208,8 +9232,6 @@ Process *schedule(Process *p, int calls) erts_smp_atomic32_set_relb(&function_calls, 0); fcalls = 0; - ASSERT(!erts_port_task_have_outstanding_io_tasks()); - #if 0 /* Not needed since we wont wait in sys schedule */ erts_sys_schedule_interrupt(0); #endif @@ -9241,7 +9263,9 @@ Process *schedule(Process *p, int calls) if (RUNQ_READ_LEN(&rq->ports.info.len)) { int have_outstanding_io; have_outstanding_io = erts_port_task_execute(rq, &esdp->current_port); - if ((have_outstanding_io && fcalls > 2*input_reductions) + if ((!erts_eager_check_io + && have_outstanding_io + && fcalls > 2*input_reductions) || rq->halt_in_progress) { /* * If we have performed more than 2*INPUT_REDUCTIONS since diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index 3b0798207e..27a3a3553b 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -104,6 +104,7 @@ struct saved_calls { }; extern Export exp_send, exp_receive, exp_timeout; +extern int erts_eager_check_io; extern int erts_sched_compact_load; extern int erts_sched_balance_util; extern Uint erts_no_schedulers; diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index ae053fc191..9ae973e108 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -7274,6 +7274,18 @@ driver_system_info(ErlDrvSysInfo *sip, size_t si_size) sip->nif_major_version = ERL_NIF_MAJOR_VERSION; sip->nif_minor_version = ERL_NIF_MINOR_VERSION; } + /* + * 'dirty_scheduler_support' is the last field in the 4th version + * (driver version 3.1, NIF version 2.7) + */ + if (si_size >= ERL_DRV_SYS_INFO_SIZE(dirty_scheduler_support)) { +#if defined(ERL_NIF_DIRTY_SCHEDULER_SUPPORT) && defined(USE_THREADS) + sip->dirty_scheduler_support = 1; +#else + sip->dirty_scheduler_support = 0; +#endif + } + } diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index 3d8dd9c6d0..c29d4b3777 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -66,8 +66,12 @@ */ #ifndef ERTS_SYS_FD_TYPE +#define ERTS_SYS_FD_INVALID ((ErtsSysFdType) -1) typedef int ErtsSysFdType; #else +#ifndef ERTS_SYS_FD_INVALID +# error missing ERTS_SYS_FD_INVALID +#endif typedef ERTS_SYS_FD_TYPE ErtsSysFdType; #endif @@ -501,7 +505,7 @@ extern volatile int erts_writing_erl_crash_dump; # define NO_ERF # define NO_ERFC /* This definition doesn't take NaN into account, but matherr() gets those */ -# define finite(x) (fabs(x) != HUGE_VAL) +# define isfinite(x) (fabs(x) != HUGE_VAL) # define USE_MATHERR # define HAVE_FINITE #endif @@ -744,6 +748,14 @@ void init_getenv_state(GETENV_STATE *); char * getenv_string(GETENV_STATE *); void fini_getenv_state(GETENV_STATE *); +#define HAVE_ERTS_CHECK_IO_DEBUG +typedef struct { + int no_used_fds; + int no_driver_select_structs; + int no_driver_event_structs; +} ErtsCheckIoDebugInfo; +int erts_check_io_debug(ErtsCheckIoDebugInfo *ip); + /* xxxP */ #define SYS_DEFAULT_FLOAT_DECIMALS 20 void init_sys_float(void); diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c index 55f9e68e78..f20e6e5665 100644 --- a/erts/emulator/beam/utils.c +++ b/erts/emulator/beam/utils.c @@ -48,6 +48,7 @@ #include "erl_sched_spec_pre_alloc.h" #include "beam_bp.h" #include "erl_ptab.h" +#include "erl_check_io.h" #undef M_TRIM_THRESHOLD #undef M_TOP_PAD diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c index a321bb9641..7e4043fc1b 100644 --- a/erts/emulator/drivers/win32/win_efile.c +++ b/erts/emulator/drivers/win32/win_efile.c @@ -1288,6 +1288,10 @@ do_fileinfo(Efile_call_state* state, Efile_info* pInfo, { HANDLE handle; /* Handle returned by CreateFile() */ BY_HANDLE_FILE_INFORMATION fileInfo; /* from CreateFile() */ + + /* We initialise nNumberOfLinks as GetFileInformationByHandle + does not always initialise this field */ + fileInfo.nNumberOfLinks = 1; if (handle = CreateFileW(name, GENERIC_READ, FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, 0, NULL)) { GetFileInformationByHandle(handle, &fileInfo); diff --git a/erts/emulator/hipe/hipe_amd64_bifs.m4 b/erts/emulator/hipe/hipe_amd64_bifs.m4 index 0de69a617f..a3219c7586 100644 --- a/erts/emulator/hipe/hipe_amd64_bifs.m4 +++ b/erts/emulator/hipe/hipe_amd64_bifs.m4 @@ -39,7 +39,10 @@ define(HANDLE_GOT_MBUF,` jmp 2b') `#if defined(ERTS_ENABLE_LOCK_CHECK) && defined(ERTS_SMP) -# define CALL_BIF(F) movq $CSYM(F), P_BIF_CALLEE(P); call CSYM(hipe_debug_bif_wrapper) +# define CALL_BIF(F) \ + movq CSYM(F)@GOTPCREL(%rip), %r11; \ + movq %r11, P_BIF_CALLEE(P); \ + call CSYM(hipe_debug_bif_wrapper) #else # define CALL_BIF(F) call CSYM(F) #endif' diff --git a/erts/emulator/hipe/hipe_bif0.c b/erts/emulator/hipe/hipe_bif0.c index 2497d51df1..c9eee2acf2 100644 --- a/erts/emulator/hipe/hipe_bif0.c +++ b/erts/emulator/hipe/hipe_bif0.c @@ -1022,7 +1022,7 @@ BIF_RETTYPE hipe_conv_big_to_float(BIF_ALIST_1) */ void hipe_emulate_fpe(Process* p) { - if (!finite(p->hipe.float_result)) { + if (!isfinite(p->hipe.float_result)) { p->fp_exception = 1; } } diff --git a/erts/emulator/hipe/hipe_mode_switch.c b/erts/emulator/hipe/hipe_mode_switch.c index 4ddc2790b1..1ae1d17b7f 100644 --- a/erts/emulator/hipe/hipe_mode_switch.c +++ b/erts/emulator/hipe/hipe_mode_switch.c @@ -2,7 +2,7 @@ * %CopyrightBegin% * - * Copyright Ericsson AB 2001-2013. All Rights Reserved. + * Copyright Ericsson AB 2001-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -187,6 +187,9 @@ void hipe_set_call_trap(Uint *bfun, void *nfun, int is_closure) void hipe_reserve_beam_trap_frame(Process *p, Eterm reg[], unsigned arity) { + if (!hipe_bifcall_from_native_is_recursive(p)) + return; + /* ensure that at least 2 words are available on the BEAM stack */ if ((p->stop - 2) < p->htop) { DPRINTF("calling gc to reserve BEAM stack size"); @@ -195,25 +198,26 @@ void hipe_reserve_beam_trap_frame(Process *p, Eterm reg[], unsigned arity) } p->stop -= 2; p->stop[0] = NIL; - p->stop[1] = NIL; + p->stop[1] = hipe_beam_catch_throw; } static __inline__ void hipe_push_beam_trap_frame(Process *p, Eterm reg[], unsigned arity) { - if (p->flags & F_DISABLE_GC) { + if (&p->stop[1] < p->hend && p->stop[1] == hipe_beam_catch_throw) { /* Trap frame already reserved */ - ASSERT(p->stop[0] == NIL && p->stop[1] == NIL); + ASSERT(p->stop[0] == NIL); } else { + ASSERT(!(p->flags & F_DISABLE_GC)); if ((p->stop - 2) < p->htop) { DPRINTF("calling gc to increase BEAM stack size"); p->fcalls -= erts_garbage_collect(p, 2, reg, arity); ASSERT(!((p->stop - 2) < p->htop)); } p->stop -= 2; + p->stop[1] = hipe_beam_catch_throw; } - p->stop[1] = hipe_beam_catch_throw; p->stop[0] = make_cp(p->cp); ++p->catches; p->cp = hipe_beam_pc_return; @@ -221,12 +225,16 @@ hipe_push_beam_trap_frame(Process *p, Eterm reg[], unsigned arity) void hipe_unreserve_beam_trap_frame(Process *p) { - ASSERT(p->stop[0] == NIL && p->stop[1] == NIL); + if (!hipe_bifcall_from_native_is_recursive(p)) + return; + + ASSERT(p->stop[0] == NIL && p->stop[1] == hipe_beam_catch_throw); p->stop += 2; } static __inline__ void hipe_pop_beam_trap_frame(Process *p) { + ASSERT(p->stop[1] == hipe_beam_catch_throw); p->cp = cp_val(p->stop[0]); --p->catches; p->stop += 2; diff --git a/erts/emulator/hipe/hipe_risc_glue.h b/erts/emulator/hipe/hipe_risc_glue.h index cc2671c016..dbb7086dae 100644 --- a/erts/emulator/hipe/hipe_risc_glue.h +++ b/erts/emulator/hipe/hipe_risc_glue.h @@ -214,6 +214,14 @@ hipe_trap_from_native_is_recursive(Process *p) return 0; } +/* Native called BIF. Is it a recursive call? + i.e should we return back to native when BIF is done? */ +static __inline__ int +hipe_bifcall_from_native_is_recursive(Process *p) +{ + return (p->hipe.nra != (void(*)(void))&nbif_return); +} + /* Native makes a call which needs to unload the parameters. This differs from hipe_call_from_native_is_recursive() in diff --git a/erts/emulator/hipe/hipe_x86_glue.h b/erts/emulator/hipe/hipe_x86_glue.h index 63ad250d60..4b6e495b9a 100644 --- a/erts/emulator/hipe/hipe_x86_glue.h +++ b/erts/emulator/hipe/hipe_x86_glue.h @@ -207,6 +207,14 @@ hipe_trap_from_native_is_recursive(Process *p) return 0; } +/* Native called BIF. Is it a recursive call? + i.e should we return back to native when BIF is done? */ +static __inline__ int +hipe_bifcall_from_native_is_recursive(Process *p) +{ + return (*p->hipe.nsp != (Eterm)nbif_return); +} + /* Native makes a call which needs to unload the parameters. This differs from hipe_call_from_native_is_recursive() in diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c index 1db673e7f3..81cb5dc4bb 100644 --- a/erts/emulator/sys/common/erl_check_io.c +++ b/erts/emulator/sys/common/erl_check_io.c @@ -52,8 +52,17 @@ typedef char EventStateType; #define ERTS_EV_TYPE_STOP_USE ((EventStateType) 3) /* pending stop_select */ typedef char EventStateFlags; -#define ERTS_EV_FLAG_USED ((EventStateFlags) 1) /* ERL_DRV_USE has been turned on */ +#define ERTS_EV_FLAG_USED ((EventStateFlags) 1) /* ERL_DRV_USE has been turned on */ +#define ERTS_EV_FLAG_DEFER_IN_EV ((EventStateFlags) 2) +#define ERTS_EV_FLAG_DEFER_OUT_EV ((EventStateFlags) 4) +#ifdef DEBUG +# define ERTS_ACTIVE_FD_INC 2 +#else +# define ERTS_ACTIVE_FD_INC 128 +#endif + +#define ERTS_CHECK_IO_POLL_RES_LEN 512 #if defined(ERTS_KERNEL_POLL_VERSION) # define ERTS_CIO_EXPORT(FUNC) FUNC ## _kp @@ -67,6 +76,7 @@ typedef char EventStateFlags; (ERTS_POLL_USE_POLL && !ERTS_POLL_USE_KERNEL_POLL) #define ERTS_CIO_POLL_CTL ERTS_POLL_EXPORT(erts_poll_control) +#define ERTS_CIO_POLL_CTLV ERTS_POLL_EXPORT(erts_poll_controlv) #define ERTS_CIO_POLL_WAIT ERTS_POLL_EXPORT(erts_poll_wait) #ifdef ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT #define ERTS_CIO_POLL_AS_INTR ERTS_POLL_EXPORT(erts_poll_async_sig_interrupt) @@ -85,6 +95,13 @@ static struct pollset_info { ErtsPollSet ps; erts_smp_atomic_t in_poll_wait; /* set while doing poll */ + struct { + int six; /* start index */ + int eix; /* end index */ + erts_smp_atomic32_t no; + int size; + ErtsSysFdType *array; + } active_fd; #ifdef ERTS_SMP struct removed_fd* removed_list; /* list of deselected fd's*/ erts_smp_spinlock_t removed_list_lock; @@ -97,9 +114,11 @@ typedef struct { SafeHashBucket hb; #endif ErtsSysFdType fd; - union { - ErtsDrvEventDataState *event; /* ERTS_EV_TYPE_DRV_EV */ + struct { ErtsDrvSelectDataState *select; /* ERTS_EV_TYPE_DRV_SEL */ +#if ERTS_CIO_HAVE_DRV_EVENT + ErtsDrvEventDataState *event; /* ERTS_EV_TYPE_DRV_EV */ +#endif erts_driver_t* drv_ptr; /* ERTS_EV_TYPE_STOP_USE */ } driver; ErtsPollEvents events; @@ -169,6 +188,10 @@ static ERTS_INLINE ErtsDrvEventState* hash_new_drv_ev_state(ErtsSysFdType fd) ErtsDrvEventState tmpl; tmpl.fd = fd; tmpl.driver.select = NULL; +#if ERTS_CIO_HAVE_DRV_EVENT + tmpl.driver.event = NULL; +#endif + tmpl.driver.drv_ptr = NULL; tmpl.events = 0; tmpl.remove_cnt = 0; tmpl.type = ERTS_EV_TYPE_NONE; @@ -209,6 +232,65 @@ ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(removed_fd, struct removed_fd, 64, ERTS_ALC_T_F #endif static ERTS_INLINE void +init_iotask(ErtsIoTask *io_task) +{ + erts_port_task_handle_init(&io_task->task); + erts_smp_atomic_init_nob(&io_task->executed_time, ~((erts_aint_t) 0)); +} + +static ERTS_INLINE int +is_iotask_active(ErtsIoTask *io_task, erts_aint_t current_cio_time) +{ + if (erts_port_task_is_scheduled(&io_task->task)) + return 1; + if (erts_smp_atomic_read_nob(&io_task->executed_time) == current_cio_time) + return 1; + return 0; +} + +static ERTS_INLINE ErtsDrvSelectDataState * +alloc_drv_select_data(void) +{ + ErtsDrvSelectDataState *dsp = erts_alloc(ERTS_ALC_T_DRV_SEL_D_STATE, + sizeof(ErtsDrvSelectDataState)); + dsp->inport = NIL; + dsp->outport = NIL; + init_iotask(&dsp->iniotask); + init_iotask(&dsp->outiotask); + return dsp; +} + +static ERTS_INLINE void +free_drv_select_data(ErtsDrvSelectDataState *dsp) +{ + ASSERT(!erts_port_task_is_scheduled(&dsp->iniotask.task)); + ASSERT(!erts_port_task_is_scheduled(&dsp->outiotask.task)); + erts_free(ERTS_ALC_T_DRV_SEL_D_STATE, dsp); +} + +static ERTS_INLINE ErtsDrvEventDataState * +alloc_drv_event_data(void) +{ + ErtsDrvEventDataState *dep = erts_alloc(ERTS_ALC_T_DRV_EV_D_STATE, + sizeof(ErtsDrvEventDataState)); + dep->port = NIL; + dep->data = NULL; + dep->removed_events = 0; +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + dep->deferred_events = 0; +#endif + init_iotask(&dep->iotask); + return dep; +} + +static ERTS_INLINE void +free_drv_event_data(ErtsDrvEventDataState *dep) +{ + ASSERT(!erts_port_task_is_scheduled(&dep->iotask.task)); + erts_free(ERTS_ALC_T_DRV_EV_D_STATE, dep); +} + +static ERTS_INLINE void remember_removed(ErtsDrvEventState *state, struct pollset_info* psi) { #ifdef ERTS_SMP @@ -288,7 +370,7 @@ forget_removed(struct pollset_info* psi) drv_ptr = state->driver.drv_ptr; ASSERT(drv_ptr); state->type = ERTS_EV_TYPE_NONE; - state->flags = 0; + state->flags &= ~ERTS_EV_FLAG_USED; state->driver.drv_ptr = NULL; /* Fall through */ case ERTS_EV_TYPE_NONE: @@ -345,6 +427,10 @@ grow_drv_ev_state(int min_ix) for (i = erts_smp_atomic_read_nob(&drv_ev_state_len); i < new_len; i++) { drv_ev_state[i].fd = (ErtsSysFdType) i; drv_ev_state[i].driver.select = NULL; +#if ERTS_CIO_HAVE_DRV_EVENT + drv_ev_state[i].driver.event = NULL; +#endif + drv_ev_state[i].driver.drv_ptr = NULL; drv_ev_state[i].events = 0; drv_ev_state[i].remove_cnt = 0; drv_ev_state[i].type = ERTS_EV_TYPE_NONE; @@ -365,11 +451,7 @@ grow_drv_ev_state(int min_ix) static ERTS_INLINE void abort_task(Eterm id, ErtsPortTaskHandle *pthp, EventStateType type) { - if (is_nil(id)) { - ASSERT(type == ERTS_EV_TYPE_NONE - || !erts_port_task_is_scheduled(pthp)); - } - else if (erts_port_task_is_scheduled(pthp)) { + if (is_not_nil(id) && erts_port_task_is_scheduled(pthp)) { erts_port_task_abort(pthp); ASSERT(erts_is_port_alive(id)); } @@ -384,7 +466,7 @@ abort_tasks(ErtsDrvEventState *state, int mode) #if ERTS_CIO_HAVE_DRV_EVENT case ERTS_EV_TYPE_DRV_EV: abort_task(state->driver.event->port, - &state->driver.event->task, + &state->driver.event->iotask.task, ERTS_EV_TYPE_DRV_EV); return; #endif @@ -398,14 +480,14 @@ abort_tasks(ErtsDrvEventState *state, int mode) case ERL_DRV_WRITE: ASSERT(state->type == ERTS_EV_TYPE_DRV_SEL); abort_task(state->driver.select->outport, - &state->driver.select->outtask, + &state->driver.select->outiotask.task, state->type); if (mode == ERL_DRV_WRITE) break; case ERL_DRV_READ: ASSERT(state->type == ERTS_EV_TYPE_DRV_SEL); abort_task(state->driver.select->inport, - &state->driver.select->intask, + &state->driver.select->iniotask.task, state->type); break; default: @@ -443,16 +525,14 @@ deselect(ErtsDrvEventState *state, int mode) if (!(state->events)) { switch (state->type) { case ERTS_EV_TYPE_DRV_SEL: - ASSERT(!erts_port_task_is_scheduled(&state->driver.select->intask)); - ASSERT(!erts_port_task_is_scheduled(&state->driver.select->outtask)); - erts_free(ERTS_ALC_T_DRV_SEL_D_STATE, - state->driver.select); + state->driver.select->inport = NIL; + state->driver.select->outport = NIL; break; #if ERTS_CIO_HAVE_DRV_EVENT case ERTS_EV_TYPE_DRV_EV: - ASSERT(!erts_port_task_is_scheduled(&state->driver.event->task)); - erts_free(ERTS_ALC_T_DRV_EV_D_STATE, - state->driver.event); + state->driver.event->port = NIL; + state->driver.event->data = NULL; + state->driver.event->removed_events = (ErtsPollEvents) 0; break; #endif case ERTS_EV_TYPE_NONE: @@ -462,20 +542,297 @@ deselect(ErtsDrvEventState *state, int mode) break; } - state->driver.select = NULL; state->type = ERTS_EV_TYPE_NONE; - state->flags = 0; + state->flags &= ~ERTS_EV_FLAG_USED; remember_removed(state, &pollset); } } - #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS # define IS_FD_UNKNOWN(state) ((state)->type == ERTS_EV_TYPE_NONE && (state)->remove_cnt == 0) #else # define IS_FD_UNKNOWN(state) ((state) == NULL) #endif +static ERTS_INLINE void +check_fd_cleanup(ErtsDrvEventState *state, +#if ERTS_CIO_HAVE_DRV_EVENT + ErtsDrvEventDataState **free_event, +#endif + ErtsDrvSelectDataState **free_select) +{ + erts_aint_t current_cio_time; + + ERTS_SMP_LC_ASSERT(erts_smp_lc_mtx_is_locked(fd_mtx(state->fd))); + + current_cio_time = erts_smp_atomic_read_acqb(&erts_check_io_time); + *free_select = NULL; + if (state->driver.select + && (state->type != ERTS_EV_TYPE_DRV_SEL) + && !is_iotask_active(&state->driver.select->iniotask, current_cio_time) + && !is_iotask_active(&state->driver.select->outiotask, current_cio_time)) { + + *free_select = state->driver.select; + state->driver.select = NULL; + } + +#if ERTS_CIO_HAVE_DRV_EVENT + *free_event = NULL; + if (state->driver.event + && (state->type != ERTS_EV_TYPE_DRV_EV) + && !is_iotask_active(&state->driver.event->iotask, current_cio_time)) { + + *free_event = state->driver.event; + state->driver.event = NULL; + } +#endif + +#ifndef ERTS_SYS_CONTINOUS_FD_NUMBERS + if (((state->type != ERTS_EV_TYPE_NONE) + | state->remove_cnt +#if ERTS_CIO_HAVE_DRV_EVENT + | (state->driver.event != NULL) +#endif + | (state->driver.select != NULL)) == 0) { + + hash_erase_drv_ev_state(state); + + } +#endif +} + +static ERTS_INLINE int +check_cleanup_active_fd(ErtsSysFdType fd, +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + ErtsPollControlEntry *pce, + int *pce_ix, +#endif + erts_aint_t current_cio_time) +{ + ErtsDrvEventState *state; + int active = 0; + erts_smp_mtx_t *mtx = fd_mtx(fd); + void *free_select = NULL; +#if ERTS_CIO_HAVE_DRV_EVENT + void *free_event = NULL; +#endif +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + ErtsPollEvents evon = 0, evoff = 0; +#endif + + erts_smp_mtx_lock(mtx); + +#ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS + state = &drv_ev_state[(int) fd]; +#else + state = hash_get_drv_ev_state(fd); /* may be NULL! */ + if (state) +#endif + { + if (state->driver.select) { +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + if (is_iotask_active(&state->driver.select->iniotask, current_cio_time)) { + active = 1; + if ((state->events & ERTS_POLL_EV_IN) + && !(state->flags & ERTS_EV_FLAG_DEFER_IN_EV)) { + evoff |= ERTS_POLL_EV_IN; + state->flags |= ERTS_EV_FLAG_DEFER_IN_EV; + } + } + else if (state->flags & ERTS_EV_FLAG_DEFER_IN_EV) { + if (state->events & ERTS_POLL_EV_IN) + evon |= ERTS_POLL_EV_IN; + state->flags &= ~ERTS_EV_FLAG_DEFER_IN_EV; + } + if (is_iotask_active(&state->driver.select->outiotask, current_cio_time)) { + active = 1; + if ((state->events & ERTS_POLL_EV_OUT) + && !(state->flags & ERTS_EV_FLAG_DEFER_OUT_EV)) { + evoff |= ERTS_POLL_EV_OUT; + state->flags |= ERTS_EV_FLAG_DEFER_OUT_EV; + } + } + else if (state->flags & ERTS_EV_FLAG_DEFER_OUT_EV) { + if (state->events & ERTS_POLL_EV_OUT) + evon |= ERTS_POLL_EV_OUT; + state->flags &= ~ERTS_EV_FLAG_DEFER_OUT_EV; + } + if (active) + (void) 0; + else +#else + if (is_iotask_active(&state->driver.select->iniotask, current_cio_time) + || is_iotask_active(&state->driver.select->outiotask, current_cio_time)) + active = 1; + else +#endif + if (state->type != ERTS_EV_TYPE_DRV_SEL) { + free_select = state->driver.select; + state->driver.select = NULL; + } + } + +#if ERTS_CIO_HAVE_DRV_EVENT + if (state->driver.event) { + if (is_iotask_active(&state->driver.event->iotask, current_cio_time)) { +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + ErtsPollEvents evs = state->events & ~state->driver.event->deferred_events; + if (evs) { + evoff |= evs; + state->driver.event->deferred_events |= evs; + } +#endif + active = 1; + } + else if (state->type != ERTS_EV_TYPE_DRV_EV) { + free_event = state->driver.event; + state->driver.event = NULL; + } +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + else { + ErtsPollEvents evs = state->events & state->driver.event->deferred_events; + if (evs) { + evon |= evs; + state->driver.event->deferred_events = 0; + } + } +#endif + + } +#endif + +#ifndef ERTS_SYS_CONTINOUS_FD_NUMBERS + if (((state->type != ERTS_EV_TYPE_NONE) | state->remove_cnt | active) == 0) + hash_erase_drv_ev_state(state); +#endif + + } + + erts_smp_mtx_unlock(mtx); + + if (free_select) + free_drv_select_data(free_select); +#if ERTS_CIO_HAVE_DRV_EVENT + if (free_event) + free_drv_event_data(free_event); +#endif + +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + if (evoff) { + ErtsPollControlEntry *pcep = &pce[(*pce_ix)++]; + pcep->fd = fd; + pcep->events = evoff; + pcep->on = 0; + } + if (evon) { + ErtsPollControlEntry *pcep = &pce[(*pce_ix)++]; + pcep->fd = fd; + pcep->events = evon; + pcep->on = 1; + } +#endif + + return active; +} + +static void +check_cleanup_active_fds(erts_aint_t current_cio_time) +{ + int six = pollset.active_fd.six; + int eix = pollset.active_fd.eix; + erts_aint32_t no = erts_smp_atomic32_read_dirty(&pollset.active_fd.no); + int size = pollset.active_fd.size; + int ix = six; +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + /* every fd might add two entries */ + Uint pce_sz = 2*sizeof(ErtsPollControlEntry)*no; + ErtsPollControlEntry *pctrl_entries = (pce_sz + ? erts_alloc(ERTS_ALC_T_TMP, pce_sz) + : NULL); + int pctrl_ix = 0; +#endif + + while (ix != eix) { + ErtsSysFdType fd = pollset.active_fd.array[ix]; + int nix = ix + 1; + if (nix >= size) + nix = 0; + ASSERT(fd != ERTS_SYS_FD_INVALID); + if (!check_cleanup_active_fd(fd, +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + pctrl_entries, + &pctrl_ix, +#endif + current_cio_time)) { + no--; + if (ix == six) { +#ifdef DEBUG + pollset.active_fd.array[ix] = ERTS_SYS_FD_INVALID; +#endif + six = nix; + } + else { + pollset.active_fd.array[ix] = pollset.active_fd.array[six]; +#ifdef DEBUG + pollset.active_fd.array[six] = ERTS_SYS_FD_INVALID; +#endif + six++; + if (six >= size) + six = 0; + } + } + ix = nix; + } + +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + ASSERT(pctrl_ix <= pce_sz/sizeof(ErtsPollControlEntry)); + if (pctrl_ix) + ERTS_CIO_POLL_CTLV(pollset.ps, pctrl_entries, pctrl_ix); + if (pctrl_entries) + erts_free(ERTS_ALC_T_TMP, pctrl_entries); +#endif + + pollset.active_fd.six = six; + pollset.active_fd.eix = eix; + erts_smp_atomic32_set_relb(&pollset.active_fd.no, no); +} + +static ERTS_INLINE void +add_active_fd(ErtsSysFdType fd) +{ + int eix = pollset.active_fd.eix; + int size = pollset.active_fd.size; + + + pollset.active_fd.array[eix] = fd; + + erts_smp_atomic32_set_relb(&pollset.active_fd.no, + (erts_smp_atomic32_read_dirty(&pollset.active_fd.no) + + 1)); + + eix++; + if (eix >= size) + eix = 0; + if (pollset.active_fd.six == eix) { + pollset.active_fd.six = 0; + eix = size; + size += ERTS_ACTIVE_FD_INC; + pollset.active_fd.array = erts_realloc(ERTS_ALC_T_ACTIVE_FD_ARR, + pollset.active_fd.array, + sizeof(ErtsSysFdType)*size); + pollset.active_fd.size = size; +#ifdef DEBUG + { + int i; + for (i = eix + 1; i < size; i++) + pollset.active_fd.array[i] = ERTS_SYS_FD_INVALID; + } +#endif + + } + + pollset.active_fd.eix = eix; +} int ERTS_CIO_EXPORT(driver_select)(ErlDrvPort ix, @@ -492,6 +849,10 @@ ERTS_CIO_EXPORT(driver_select)(ErlDrvPort ix, ErtsDrvEventState *state; int wake_poller; int ret; +#if ERTS_CIO_HAVE_DRV_EVENT + ErtsDrvEventDataState *free_event = NULL; +#endif + ErtsDrvSelectDataState *free_select = NULL; #ifdef USE_VM_PROBES DTRACE_CHARBUF(name, 64); #endif @@ -593,9 +954,9 @@ ERTS_CIO_EXPORT(driver_select)(ErlDrvPort ix, if (new_events & (ERTS_POLL_EV_ERR|ERTS_POLL_EV_NVAL)) { if (state->type == ERTS_EV_TYPE_DRV_SEL && !state->events) { state->type = ERTS_EV_TYPE_NONE; - state->flags = 0; - erts_free(ERTS_ALC_T_DRV_SEL_D_STATE, state->driver.select); - state->driver.select = NULL; + state->flags &= ~ERTS_EV_FLAG_USED; + state->driver.select->inport = NIL; + state->driver.select->outport = NIL; } ret = -1; goto done; @@ -613,18 +974,10 @@ ERTS_CIO_EXPORT(driver_select)(ErlDrvPort ix, state->events = new_events; if (ctl_events) { if (on) { - if (state->type == ERTS_EV_TYPE_NONE) { - ErtsDrvSelectDataState *dsdsp - = erts_alloc(ERTS_ALC_T_DRV_SEL_D_STATE, - sizeof(ErtsDrvSelectDataState)); - dsdsp->inport = NIL; - dsdsp->outport = NIL; - erts_port_task_handle_init(&dsdsp->intask); - erts_port_task_handle_init(&dsdsp->outtask); - ASSERT(state->driver.select == NULL); - state->driver.select = dsdsp; + if (!state->driver.select) + state->driver.select = alloc_drv_select_data(); + if (state->type == ERTS_EV_TYPE_NONE) state->type = ERTS_EV_TYPE_DRV_SEL; - } ASSERT(state->type == ERTS_EV_TYPE_DRV_SEL); if (ctl_events & ERTS_POLL_EV_IN) state->driver.select->inport = id; @@ -645,17 +998,12 @@ ERTS_CIO_EXPORT(driver_select)(ErlDrvPort ix, state->driver.select->outport = NIL; } if (new_events == 0) { - ASSERT(!erts_port_task_is_scheduled(&state->driver.select->intask)); - ASSERT(!erts_port_task_is_scheduled(&state->driver.select->outtask)); if (old_events != 0) { remember_removed(state, &pollset); } if ((mode & ERL_DRV_USE) || !(state->flags & ERTS_EV_FLAG_USED)) { state->type = ERTS_EV_TYPE_NONE; - state->flags = 0; - erts_free(ERTS_ALC_T_DRV_SEL_D_STATE, - state->driver.select); - state->driver.select = NULL; + state->flags &= ~ERTS_EV_FLAG_USED; } /*else keep it, as fd will probably be selected upon again */ } @@ -686,13 +1034,15 @@ ERTS_CIO_EXPORT(driver_select)(ErlDrvPort ix, ret = 0; -done:; -#ifndef ERTS_SYS_CONTINOUS_FD_NUMBERS - if (state->type == ERTS_EV_TYPE_NONE && state->remove_cnt == 0) { - hash_erase_drv_ev_state(state); - } +done: + + check_fd_cleanup(state, +#if ERTS_CIO_HAVE_DRV_EVENT + &free_event, #endif -done_unknown: + &free_select); + +done_unknown: erts_smp_mtx_unlock(fd_mtx(fd)); if (stop_select_fn) { int was_unmasked = erts_block_fpe(); @@ -700,6 +1050,12 @@ done_unknown: (*stop_select_fn)(e, NULL); erts_unblock_fpe(was_unmasked); } + if (free_select) + free_drv_select_data(free_select); +#if ERTS_CIO_HAVE_DRV_EVENT + if (free_event) + free_drv_event_data(free_event); +#endif return ret; } @@ -719,6 +1075,10 @@ ERTS_CIO_EXPORT(driver_event)(ErlDrvPort ix, ErtsDrvEventState *state; int do_wake = 0; int ret; +#if ERTS_CIO_HAVE_DRV_EVENT + ErtsDrvEventDataState *free_event; +#endif + ErtsDrvSelectDataState *free_select; Port *prt = erts_drvport2port(ix); if (prt == ERTS_INVALID_ERL_DRV_PORT) @@ -799,10 +1159,8 @@ ERTS_CIO_EXPORT(driver_event)(ErlDrvPort ix, state->driver.event->removed_events |= remove_events; } else { - state->driver.event - = erts_alloc(ERTS_ALC_T_DRV_EV_D_STATE, - sizeof(ErtsDrvEventDataState)); - erts_port_task_handle_init(&state->driver.event->task); + if (!state->driver.event) + state->driver.event = alloc_drv_event_data(); state->driver.event->port = id; state->driver.event->removed_events = (ErtsPollEvents) 0; state->type = ERTS_EV_TYPE_DRV_EV; @@ -812,10 +1170,10 @@ ERTS_CIO_EXPORT(driver_event)(ErlDrvPort ix, else { if (state->type == ERTS_EV_TYPE_DRV_EV) { abort_tasks(state, 0); - erts_free(ERTS_ALC_T_DRV_EV_D_STATE, - state->driver.event); + state->driver.event->port = NIL; + state->driver.event->data = NULL; + state->driver.event->removed_events = (ErtsPollEvents) 0; } - state->driver.select = NULL; state->type = ERTS_EV_TYPE_NONE; remember_removed(state, &pollset); } @@ -825,12 +1183,22 @@ ERTS_CIO_EXPORT(driver_event)(ErlDrvPort ix, ret = 0; done: -#ifndef ERTS_SYS_CONTINOUS_FD_NUMBERS - if (state->type == ERTS_EV_TYPE_NONE && state->remove_cnt == 0) { - hash_erase_drv_ev_state(state); - } + + check_fd_cleanup(state, +#if ERTS_CIO_HAVE_DRV_EVENT + &free_event, #endif + &free_select); + erts_smp_mtx_unlock(fd_mtx(fd)); + + if (free_select) + free_drv_select_data(free_select); +#if ERTS_CIO_HAVE_DRV_EVENT + if (free_event) + free_drv_event_data(free_event); +#endif + return ret; #endif } @@ -1027,7 +1395,7 @@ steal_pending_stop_select(erts_dsprintf_buf_t *dsbufp, ErlDrvPort ix, * In either case stop_select should not be called. */ state->type = ERTS_EV_TYPE_NONE; - state->flags = 0; + state->flags &= ~ERTS_EV_FLAG_USED; if (state->driver.drv_ptr->handle) { erts_ddll_dereference_driver(state->driver.drv_ptr->handle); } @@ -1099,38 +1467,103 @@ event_large_fd_error(ErlDrvPort ix, ErtsSysFdType fd, ErlDrvEventData event_data #endif #endif +static ERTS_INLINE int +io_task_schedule_allowed(ErtsDrvEventState *state, + ErtsPortTaskType type, + erts_aint_t current_cio_time) +{ + ErtsIoTask *io_task; + + switch (type) { + case ERTS_PORT_TASK_INPUT: + if (!state->driver.select) + return 0; +#if ERTS_CIO_HAVE_DRV_EVENT + if (state->driver.event) + return 0; +#endif + io_task = &state->driver.select->iniotask; + break; + case ERTS_PORT_TASK_OUTPUT: + if (!state->driver.select) + return 0; +#if ERTS_CIO_HAVE_DRV_EVENT + if (state->driver.event) + return 0; +#endif + io_task = &state->driver.select->outiotask; + break; +#if ERTS_CIO_HAVE_DRV_EVENT + case ERTS_PORT_TASK_EVENT: + if (!state->driver.event) + return 0; + if (state->driver.select) + return 0; + io_task = &state->driver.event->iotask; + break; +#endif + default: + ERTS_INTERNAL_ERROR("Invalid I/O-task type"); + return 0; + } + + return !is_iotask_active(io_task, current_cio_time); +} + static ERTS_INLINE void -iready(Eterm id, ErtsDrvEventState *state) +iready(Eterm id, ErtsDrvEventState *state, erts_aint_t current_cio_time) { - if (erts_port_task_schedule(id, - &state->driver.select->intask, - ERTS_PORT_TASK_INPUT, - (ErlDrvEvent) state->fd) != 0) { - stale_drv_select(id, state, ERL_DRV_READ); + if (io_task_schedule_allowed(state, + ERTS_PORT_TASK_INPUT, + current_cio_time)) { + ErtsIoTask *iotask = &state->driver.select->iniotask; + erts_smp_atomic_set_nob(&iotask->executed_time, current_cio_time); + if (erts_port_task_schedule(id, + &iotask->task, + ERTS_PORT_TASK_INPUT, + (ErlDrvEvent) state->fd) != 0) { + stale_drv_select(id, state, ERL_DRV_READ); + } + add_active_fd(state->fd); } } static ERTS_INLINE void -oready(Eterm id, ErtsDrvEventState *state) +oready(Eterm id, ErtsDrvEventState *state, erts_aint_t current_cio_time) { - if (erts_port_task_schedule(id, - &state->driver.select->outtask, - ERTS_PORT_TASK_OUTPUT, - (ErlDrvEvent) state->fd) != 0) { - stale_drv_select(id, state, ERL_DRV_WRITE); + if (io_task_schedule_allowed(state, + ERTS_PORT_TASK_OUTPUT, + current_cio_time)) { + ErtsIoTask *iotask = &state->driver.select->outiotask; + erts_smp_atomic_set_nob(&iotask->executed_time, current_cio_time); + if (erts_port_task_schedule(id, + &iotask->task, + ERTS_PORT_TASK_OUTPUT, + (ErlDrvEvent) state->fd) != 0) { + stale_drv_select(id, state, ERL_DRV_WRITE); + } + add_active_fd(state->fd); } } #if ERTS_CIO_HAVE_DRV_EVENT static ERTS_INLINE void -eready(Eterm id, ErtsDrvEventState *state, ErlDrvEventData event_data) +eready(Eterm id, ErtsDrvEventState *state, ErlDrvEventData event_data, + erts_aint_t current_cio_time) { - if (erts_port_task_schedule(id, - &state->driver.event->task, - ERTS_PORT_TASK_EVENT, - (ErlDrvEvent) state->fd, - event_data) != 0) { - stale_drv_select(id, state, 0); + if (io_task_schedule_allowed(state, + ERTS_PORT_TASK_EVENT, + current_cio_time)) { + ErtsIoTask *iotask = &state->driver.event->iotask; + erts_smp_atomic_set_nob(&iotask->executed_time, current_cio_time); + if (erts_port_task_schedule(id, + &iotask->task, + ERTS_PORT_TASK_EVENT, + (ErlDrvEvent) state->fd, + event_data) != 0) { + stale_drv_select(id, state, 0); + } + add_active_fd(state->fd); } } #endif @@ -1161,10 +1594,11 @@ ERTS_CIO_EXPORT(erts_check_io_interrupt_timed)(int set, void ERTS_CIO_EXPORT(erts_check_io)(int do_wait) { - ErtsPollResFd pollres[256]; + ErtsPollResFd *pollres; int pollres_len; SysTimeval wait_time; int poll_ret, i; + erts_aint_t current_cio_time; restart: @@ -1181,10 +1615,24 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) wait_time.tv_usec = 0; } + /* + * No need for an atomic inc op when incrementing + * erts_check_io_time, since only one thread can + * check io at a time. + */ + current_cio_time = erts_smp_atomic_read_dirty(&erts_check_io_time); + current_cio_time++; + erts_smp_atomic_set_relb(&erts_check_io_time, current_cio_time); + + check_cleanup_active_fds(current_cio_time); + #ifdef ERTS_ENABLE_LOCK_CHECK erts_lc_check_exact(NULL, 0); /* No locks should be locked */ #endif - pollres_len = sizeof(pollres)/sizeof(ErtsPollResFd); + + pollres_len = erts_smp_atomic32_read_dirty(&pollset.active_fd.no) + ERTS_CHECK_IO_POLL_RES_LEN; + + pollres = erts_alloc(ERTS_ALC_T_TMP, sizeof(ErtsPollResFd)*pollres_len); erts_smp_atomic_set_nob(&pollset.in_poll_wait, 1); @@ -1204,6 +1652,7 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) if (poll_ret != 0) { erts_smp_atomic_set_nob(&pollset.in_poll_wait, 0); forget_removed(&pollset); + erts_free(ERTS_ALC_T_TMP, pollres); if (poll_ret == EAGAIN) { goto restart; } @@ -1263,15 +1712,15 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) if ((revents & ERTS_POLL_EV_IN) || (!(revents & ERTS_POLL_EV_OUT) && state->events & ERTS_POLL_EV_IN)) { - iready(state->driver.select->inport, state); + iready(state->driver.select->inport, state, current_cio_time); } else if (state->events & ERTS_POLL_EV_OUT) { - oready(state->driver.select->outport, state); + oready(state->driver.select->outport, state, current_cio_time); } } else if (revents & (ERTS_POLL_EV_IN|ERTS_POLL_EV_OUT)) { if (revents & ERTS_POLL_EV_OUT) { - oready(state->driver.select->outport, state); + oready(state->driver.select->outport, state, current_cio_time); } /* Someone might have deselected input since revents was read (true also on the non-smp emulator since @@ -1279,7 +1728,7 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) revents... */ revents &= ~(~state->events & ERTS_POLL_EV_IN); if (revents & ERTS_POLL_EV_IN) { - iready(state->driver.select->inport, state); + iready(state->driver.select->inport, state, current_cio_time); } } else if (revents & ERTS_POLL_EV_NVAL) { @@ -1287,6 +1736,7 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) state->driver.select->inport, state->driver.select->outport, state->events); + add_active_fd(state->fd); } break; } @@ -1304,8 +1754,7 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) if (revents) { event_data->events = state->events; event_data->revents = revents; - - eready(state->driver.event->port, state, event_data); + eready(state->driver.event->port, state, event_data, current_cio_time); } break; } @@ -1323,6 +1772,7 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) (int) state->type); ASSERT(0); deselect(state, 0); + add_active_fd(state->fd); break; } } @@ -1334,6 +1784,7 @@ ERTS_CIO_EXPORT(erts_check_io)(int do_wait) } erts_smp_atomic_set_nob(&pollset.in_poll_wait, 0); + erts_free(ERTS_ALC_T_TMP, pollres); forget_removed(&pollset); } @@ -1469,10 +1920,27 @@ static void drv_ev_state_free(void *des) void ERTS_CIO_EXPORT(erts_init_check_io)(void) { + erts_smp_atomic_init_nob(&erts_check_io_time, 0); erts_smp_atomic_init_nob(&pollset.in_poll_wait, 0); + ERTS_CIO_POLL_INIT(); pollset.ps = ERTS_CIO_NEW_POLLSET(); + pollset.active_fd.six = 0; + pollset.active_fd.eix = 0; + erts_smp_atomic32_init_nob(&pollset.active_fd.no, 0); + pollset.active_fd.size = ERTS_ACTIVE_FD_INC; + pollset.active_fd.array = erts_alloc(ERTS_ALC_T_ACTIVE_FD_ARR, + sizeof(ErtsSysFdType)*ERTS_ACTIVE_FD_INC); +#ifdef DEBUG + { + int i; + for (i = 0; i < ERTS_ACTIVE_FD_INC; i++) + pollset.active_fd.array[i] = ERTS_SYS_FD_INVALID; + } +#endif + + #ifdef ERTS_SMP init_removed_fd_alloc(); pollset.removed_list = NULL; @@ -1548,12 +2016,27 @@ Eterm ERTS_CIO_EXPORT(erts_check_io_info)(void *proc) { Process *p = (Process *) proc; - Eterm tags[15], values[15], res; + Eterm tags[16], values[16], res; Uint sz, *szp, *hp, **hpp, memory_size; Sint i; ErtsPollInfo pi; - - ERTS_CIO_POLL_INFO(pollset.ps, &pi); + erts_aint_t cio_time = erts_smp_atomic_read_acqb(&erts_check_io_time); + int active_fds = (int) erts_smp_atomic32_read_acqb(&pollset.active_fd.no); + + while (1) { + erts_aint_t post_cio_time; + int post_active_fds; + + ERTS_CIO_POLL_INFO(pollset.ps, &pi); + + post_cio_time = erts_smp_atomic_read_mb(&erts_check_io_time); + post_active_fds = (int) erts_smp_atomic32_read_acqb(&pollset.active_fd.no); + if (cio_time == post_cio_time && active_fds == post_active_fds) + break; + cio_time = post_cio_time; + active_fds = post_active_fds; + } + memory_size = pi.memory_size; #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS memory_size += sizeof(ErtsDrvEventState) * erts_smp_atomic_read_nob(&drv_ev_state_len); @@ -1617,6 +2100,9 @@ ERTS_CIO_EXPORT(erts_check_io_info)(void *proc) tags[i] = erts_bld_atom(hpp, szp, "max_fds"); values[i++] = erts_bld_uint(hpp, szp, (Uint) pi.max_fds); + tags[i] = erts_bld_atom(hpp, szp, "active_fds"); + values[i++] = erts_bld_uint(hpp, szp, (Uint) active_fds); + #ifdef ERTS_POLL_COUNT_AVOIDED_WAKEUPS tags[i] = erts_bld_atom(hpp, szp, "no_avoided_wakeups"); values[i++] = erts_bld_uint(hpp, szp, (Uint) pi.no_avoided_wakeups); @@ -1671,6 +2157,8 @@ print_events(ErtsPollEvents ev) typedef struct { int used_fds; int num_errors; + int no_driver_select_structs; + int no_driver_event_structs; #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS int internal_fds; ErtsPollEvents *epep; @@ -1693,6 +2181,13 @@ static void doit_erts_check_io_debug(void *vstate, void *vcounters) struct stat stat_buf; #endif + if (state->driver.select) + counters->no_driver_select_structs++; +#if ERTS_CIO_HAVE_DRV_EVENT + if (state->driver.event) + counters->no_driver_event_structs++; +#endif + #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS if (state->events || ep_events) { if (ep_events & ERTS_POLL_EV_NVAL) { @@ -1831,6 +2326,7 @@ static void doit_erts_check_io_debug(void *vstate, void *vcounters) } } } +#if ERTS_CIO_HAVE_DRV_EVENT else if (state->type == ERTS_EV_TYPE_DRV_EV) { Eterm id; erts_printf("driver_event "); @@ -1866,6 +2362,7 @@ static void doit_erts_check_io_debug(void *vstate, void *vcounters) erts_free_port_names(pnp); } } +#endif #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS else if (internal) { erts_printf("internal "); @@ -1905,7 +2402,7 @@ static void doit_erts_check_io_debug(void *vstate, void *vcounters) } int -ERTS_CIO_EXPORT(erts_check_io_debug)(void) +ERTS_CIO_EXPORT(erts_check_io_debug)(ErtsCheckIoDebugInfo *ciodip) { #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS int fd, len; @@ -1915,6 +2412,10 @@ ERTS_CIO_EXPORT(erts_check_io_debug)(void) ErtsDrvEventState null_des; null_des.driver.select = NULL; +#if ERTS_CIO_HAVE_DRV_EVENT + null_des.driver.event = NULL; +#endif + null_des.driver.drv_ptr = NULL; null_des.events = 0; null_des.remove_cnt = 0; null_des.type = ERTS_EV_TYPE_NONE; @@ -1935,6 +2436,8 @@ ERTS_CIO_EXPORT(erts_check_io_debug)(void) #endif counters.used_fds = 0; counters.num_errors = 0; + counters.no_driver_select_structs = 0; + counters.no_driver_event_structs = 0; #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS len = erts_smp_atomic_read_nob(&drv_ev_state_len); @@ -1951,8 +2454,16 @@ ERTS_CIO_EXPORT(erts_check_io_debug)(void) erts_smp_thr_progress_unblock(); + ciodip->no_used_fds = counters.used_fds; + ciodip->no_driver_select_structs = counters.no_driver_select_structs; + ciodip->no_driver_event_structs = counters.no_driver_event_structs; + erts_printf("\n"); erts_printf("used fds=%d\n", counters.used_fds); + erts_printf("Number of driver_select() structures=%d\n", counters.no_driver_select_structs); +#if ERTS_CIO_HAVE_DRV_EVENT + erts_printf("Number of driver_event() structures=%d\n", counters.no_driver_event_structs); +#endif #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS erts_printf("internal fds=%d\n", counters.internal_fds); #endif @@ -1961,6 +2472,7 @@ ERTS_CIO_EXPORT(erts_check_io_debug)(void) #ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS erts_free(ERTS_ALC_T_TMP, (void *) counters.epep); #endif + return counters.num_errors; } diff --git a/erts/emulator/sys/common/erl_check_io.h b/erts/emulator/sys/common/erl_check_io.h index edab7947ba..d01297d55c 100644 --- a/erts/emulator/sys/common/erl_check_io.h +++ b/erts/emulator/sys/common/erl_check_io.h @@ -26,6 +26,7 @@ #ifndef ERL_CHECK_IO_H__ #define ERL_CHECK_IO_H__ +#include "sys.h" #include "erl_sys_driver.h" #ifdef ERTS_ENABLE_KERNEL_POLL @@ -52,8 +53,8 @@ void erts_check_io_kp(int); void erts_check_io_nkp(int); void erts_init_check_io_kp(void); void erts_init_check_io_nkp(void); -int erts_check_io_debug_kp(void); -int erts_check_io_debug_nkp(void); +int erts_check_io_debug_kp(ErtsCheckIoDebugInfo *); +int erts_check_io_debug_nkp(ErtsCheckIoDebugInfo *); #else /* !ERTS_ENABLE_KERNEL_POLL */ @@ -70,6 +71,27 @@ void erts_init_check_io(void); #endif +extern erts_smp_atomic_t erts_check_io_time; + +typedef struct { + ErtsPortTaskHandle task; + erts_smp_atomic_t executed_time; +} ErtsIoTask; + +ERTS_GLB_INLINE void erts_io_notify_port_task_executed(ErtsPortTaskHandle *pthp); + +#if ERTS_GLB_INLINE_INCL_FUNC_DEF + +ERTS_GLB_INLINE void +erts_io_notify_port_task_executed(ErtsPortTaskHandle *pthp) +{ + ErtsIoTask *itp = (ErtsIoTask *) (((char *) pthp) - offsetof(ErtsIoTask, task)); + erts_aint_t ci_time = erts_smp_atomic_read_acqb(&erts_check_io_time); + erts_smp_atomic_set_relb(&itp->executed_time, ci_time); +} + +#endif + #endif /* ERL_CHECK_IO_H__ */ #if !defined(ERL_CHECK_IO_C__) && !defined(ERTS_ALLOC_C__) @@ -81,6 +103,16 @@ void erts_init_check_io(void); #include "erl_poll.h" #include "erl_port_task.h" +#ifdef __WIN32__ +/* + * Current erts_poll implementation for Windows cannot handle + * active events in the set of events polled. + */ +# define ERTS_CIO_DEFER_ACTIVE_EVENTS 1 +#else +# define ERTS_CIO_DEFER_ACTIVE_EVENTS 0 +#endif + /* * ErtsDrvEventDataState is used by driver_event() which is almost never * used. We allocate ErtsDrvEventDataState separate since we dont wan't @@ -91,13 +123,16 @@ typedef struct { Eterm port; ErlDrvEventData data; ErtsPollEvents removed_events; - ErtsPortTaskHandle task; +#if ERTS_CIO_DEFER_ACTIVE_EVENTS + ErtsPollEvents deferred_events; +#endif + ErtsIoTask iotask; } ErtsDrvEventDataState; typedef struct { Eterm inport; Eterm outport; - ErtsPortTaskHandle intask; - ErtsPortTaskHandle outtask; + ErtsIoTask iniotask; + ErtsIoTask outiotask; } ErtsDrvSelectDataState; #endif /* #ifndef ERL_CHECK_IO_INTERNAL__ */ diff --git a/erts/emulator/sys/common/erl_sys_common_misc.c b/erts/emulator/sys/common/erl_sys_common_misc.c index e3ba741058..e63f0bda54 100644 --- a/erts/emulator/sys/common/erl_sys_common_misc.c +++ b/erts/emulator/sys/common/erl_sys_common_misc.c @@ -44,6 +44,14 @@ #endif #endif +/* + * erts_check_io_time is used by the erl_check_io implementation. The + * global erts_check_io_time variable is declared here since there + * (often) exist two versions of erl_check_io (kernel-poll and + * non-kernel-poll), and we dont want two versions of this variable. + */ +erts_smp_atomic_t erts_check_io_time; + /* Written once and only once */ static int filename_encoding = ERL_FILENAME_UNKNOWN; diff --git a/erts/emulator/sys/unix/erl_unix_sys.h b/erts/emulator/sys/unix/erl_unix_sys.h index 176fc049a7..c3dba69acb 100644 --- a/erts/emulator/sys/unix/erl_unix_sys.h +++ b/erts/emulator/sys/unix/erl_unix_sys.h @@ -135,9 +135,6 @@ /* File descriptors are numbers anc consecutively allocated on Unix */ #define ERTS_SYS_CONTINOUS_FD_NUMBERS -#define HAVE_ERTS_CHECK_IO_DEBUG -int erts_check_io_debug(void); - #ifndef ERTS_SMP # undef ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT # define ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT @@ -230,8 +227,13 @@ extern void sys_stop_cat(void); */ #ifdef USE_ISINF_ISNAN /* simulate finite() */ -# define finite(f) (!isinf(f) && !isnan(f)) -# define HAVE_FINITE +# define isfinite(f) (!isinf(f) && !isnan(f)) +# define HAVE_ISFINITE +#elif defined(isfinite) && !defined(HAVE_ISFINITE) +# define HAVE_ISFINITE +#elif !defined(HAVE_ISFINITE) && defined(HAVE_FINITE) +# define isfinite finite +# define HAVE_ISFINITE #endif #ifdef NO_FPE_SIGNALS @@ -241,7 +243,7 @@ extern void sys_stop_cat(void); #define erts_thread_init_fp_exception() do{}while(0) #endif # define __ERTS_FP_CHECK_INIT(fpexnp) do {} while (0) -# define __ERTS_FP_ERROR(fpexnp, f, Action) if (!finite(f)) { Action; } else {} +# define __ERTS_FP_ERROR(fpexnp, f, Action) if (!isfinite(f)) { Action; } else {} # define __ERTS_FP_ERROR_THOROUGH(fpexnp, f, Action) __ERTS_FP_ERROR(fpexnp, f, Action) # define __ERTS_SAVE_FP_EXCEPTION(fpexnp) # define __ERTS_RESTORE_FP_EXCEPTION(fpexnp) @@ -305,7 +307,7 @@ static __inline__ void __ERTS_FP_CHECK_INIT(volatile unsigned long *fp_exception code to always throw floating-point exceptions on errors. */ static __inline__ int erts_check_fpe_thorough(volatile unsigned long *fp_exception, double f) { - return erts_check_fpe(fp_exception, f) || !finite(f); + return erts_check_fpe(fp_exception, f) || !isfinite(f); } # define __ERTS_FP_ERROR_THOROUGH(fpexnp, f, Action) \ do { if (erts_check_fpe_thorough((fpexnp),(f))) { Action; } } while (0) diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c index c3d7440409..0d677d5f34 100644 --- a/erts/emulator/sys/unix/sys.c +++ b/erts/emulator/sys/unix/sys.c @@ -284,7 +284,7 @@ struct { void (*check_io)(int); Uint (*size)(void); Eterm (*info)(void *); - int (*check_io_debug)(void); + int (*check_io_debug)(ErtsCheckIoDebugInfo *); } io_func = {0}; @@ -306,9 +306,9 @@ Eterm erts_check_io_info(void *p) } int -erts_check_io_debug(void) +erts_check_io_debug(ErtsCheckIoDebugInfo *ip) { - return (*io_func.check_io_debug)(); + return (*io_func.check_io_debug)(ip); } diff --git a/erts/emulator/sys/win32/erl_poll.c b/erts/emulator/sys/win32/erl_poll.c index 7a1d129cd5..972170d465 100644 --- a/erts/emulator/sys/win32/erl_poll.c +++ b/erts/emulator/sys/win32/erl_poll.c @@ -1085,7 +1085,7 @@ void erts_poll_controlv(ErtsPollSet ps, pcev[i].events, pcev[i].on); } - ERTS_POLLSET_LOCK(ps); + ERTS_POLLSET_UNLOCK(ps); HARDTRACEF(("Out erts_poll_controlv")); } diff --git a/erts/emulator/sys/win32/erl_win_sys.h b/erts/emulator/sys/win32/erl_win_sys.h index a78dbf64af..838f0c61eb 100644 --- a/erts/emulator/sys/win32/erl_win_sys.h +++ b/erts/emulator/sys/win32/erl_win_sys.h @@ -113,12 +113,10 @@ /* * Our own type of "FD's" */ +#define ERTS_SYS_FD_INVALID INVALID_HANDLE_VALUE #define ERTS_SYS_FD_TYPE HANDLE #define NO_FSTAT_ON_SYS_FD_TYPE 1 /* They are events, not files */ -#define HAVE_ERTS_CHECK_IO_DEBUG -int erts_check_io_debug(void); - /* * For erl_time_sup */ diff --git a/erts/emulator/test/a_SUITE.erl b/erts/emulator/test/a_SUITE.erl index 195c9c0a5f..17579be416 100644 --- a/erts/emulator/test/a_SUITE.erl +++ b/erts/emulator/test/a_SUITE.erl @@ -97,23 +97,13 @@ display_check_io(ChkIo) -> catch erlang:display('--- CHECK IO INFO ---'), catch erlang:display(ChkIo), catch erts_debug:set_internal_state(available_internal_state, true), - NoOfErrorFds = (catch erts_debug:get_internal_state(check_io_debug)), + NoOfErrorFds = (catch element(1, erts_debug:get_internal_state(check_io_debug))), catch erlang:display({'NoOfErrorFds', NoOfErrorFds}), catch erts_debug:set_internal_state(available_internal_state, false), catch erlang:display('--- CHECK IO INFO ---'), ok. get_check_io_info() -> - ChkIo = erlang:system_info(check_io), - case lists:keysearch(pending_updates, 1, ChkIo) of - {value, {pending_updates, 0}} -> - display_check_io(ChkIo), - ChkIo; - false -> - ChkIo; - _ -> - receive after 10 -> ok end, - get_check_io_info() - end. + z_SUITE:get_check_io_info(). diff --git a/erts/emulator/test/driver_SUITE.erl b/erts/emulator/test/driver_SUITE.erl index 344bde7c91..8d2c620be0 100644 --- a/erts/emulator/test/driver_SUITE.erl +++ b/erts/emulator/test/driver_SUITE.erl @@ -31,8 +31,9 @@ end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2, + + a_test/1, outputv_echo/1, - timer_measure/1, timer_cancel/1, timer_change/1, @@ -79,7 +80,8 @@ thr_free_drv/1, async_blast/1, thr_msg_blast/1, - consume_timeslice/1]). + consume_timeslice/1, + z_test/1]). -export([bin_prefix/2]). @@ -122,19 +124,19 @@ init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> _ -> erts_debug:set_internal_state(available_internal_state, true) end, erlang:display({init_per_testcase, Case}), - ?line 0 = erts_debug:get_internal_state(check_io_debug), + ?line 0 = element(1, erts_debug:get_internal_state(check_io_debug)), [{watchdog, Dog},{testcase, Case}|Config]. end_per_testcase(Case, Config) -> Dog = ?config(watchdog, Config), erlang:display({end_per_testcase, Case}), - ?line 0 = erts_debug:get_internal_state(check_io_debug), + ?line 0 = element(1, erts_debug:get_internal_state(check_io_debug)), ?t:timetrap_cancel(Dog). suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> - [outputv_errors, outputv_echo, queue_echo, {group, timer}, +all() -> %% Keep a_test first and z_test last... + [a_test, outputv_errors, outputv_echo, queue_echo, {group, timer}, driver_unloaded, io_ready_exit, use_fallback_pollset, bad_fd_in_pollset, driver_event, fd_change, steal_control, otp_6602, driver_system_info_base_ver, @@ -151,7 +153,8 @@ all() -> thr_free_drv, async_blast, thr_msg_blast, - consume_timeslice]. + consume_timeslice, + z_test]. groups() -> [{timer, [], @@ -917,8 +920,7 @@ steal_control_test(Hndl = {erts_poll_info, Before}) -> end. chkio_test_init(Config) when is_list(Config) -> - ?line wait_until_no_pending_updates(), - ?line ChkIo = erlang:system_info(check_io), + ?line ChkIo = get_stable_check_io_info(), ?line case catch lists:keysearch(name, 1, ChkIo) of {value, {name, erts_poll}} -> ?line ?t:format("Before test: ~p~n", [ChkIo]), @@ -937,8 +939,7 @@ chkio_test_fini({skipped, _} = Res) -> chkio_test_fini({chkio_test_result, Res, Before}) -> ?line ok = erl_ddll:unload_driver('chkio_drv'), ?line ok = erl_ddll:stop(), - ?line wait_until_no_pending_updates(), - ?line After = erlang:system_info(check_io), + ?line After = get_stable_check_io_info(), ?line ?t:format("After test: ~p~n", [After]), ?line verify_chkio_state(Before, After), ?line Res. @@ -985,7 +986,7 @@ chkio_test({erts_poll_info, Before}, ?line Fun(), ?line During = erlang:system_info(check_io), ?line erlang:display(During), - ?line 0 = erts_debug:get_internal_state(check_io_debug), + ?line 0 = element(1, erts_debug:get_internal_state(check_io_debug)), ?line ?t:format("During test: ~p~n", [During]), ?line chk_chkio_port(Port), ?line case erlang:port_control(Port, ?CHKIO_STOP, "") of @@ -1034,18 +1035,22 @@ verify_chkio_state(Before, After) -> After) end, ?line ok. - - -wait_until_no_pending_updates() -> - case lists:keysearch(pending_updates, 1, erlang:system_info(check_io)) of - {value, {pending_updates, 0}} -> - ok; - false -> - ok; +get_stable_check_io_info() -> + ChkIo = erlang:system_info(check_io), + PendUpdNo = case lists:keysearch(pending_updates, 1, ChkIo) of + {value, {pending_updates, PendNo}} -> + PendNo; + false -> + 0 + end, + {value, {active_fds, ActFds}} = lists:keysearch(active_fds, 1, ChkIo), + case {PendUpdNo, ActFds} of + {0, 0} -> + ChkIo; _ -> receive after 10 -> ok end, - wait_until_no_pending_updates() + get_stable_check_io_info() end. otp_6602(doc) -> ["Missed port lock when stealing control of fd from a " @@ -1084,7 +1089,15 @@ otp_6602(Config) when is_list(Config) -> ["async_thrs", "sched_thrs"])). --define(EXPECTED_SYSTEM_INFO_NAMES, ?EXPECTED_SYSTEM_INFO_NAMES2). +-define(EXPECTED_SYSTEM_INFO_NAMES3, + (?EXPECTED_SYSTEM_INFO_NAMES2 ++ + ["emu_nif_vsn"])). + +-define(EXPECTED_SYSTEM_INFO_NAMES4, + (?EXPECTED_SYSTEM_INFO_NAMES3 ++ + ["dirty_sched"])). + +-define(EXPECTED_SYSTEM_INFO_NAMES, ?EXPECTED_SYSTEM_INFO_NAMES4). 'driver_system_info_base_ver'(doc) -> []; @@ -1132,16 +1145,18 @@ check_driver_system_info_result(Result) -> drv_vsn_str2tup(erlang:system_info(driver_version))} of {DDVSN, DDVSN} -> ?line [] = Ns; - {{1, 0}, _} -> - ?line ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES - -- ?EXPECTED_SYSTEM_INFO_NAMES1), - ?line ExpNs = lists:sort(Ns); - {{1, 1}, _} -> + %% {{1, 0}, _} -> + %% ?line ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES + %% -- ?EXPECTED_SYSTEM_INFO_NAMES1), + %% ?line ExpNs = lists:sort(Ns); + %% {{1, 1}, _} -> + %% ?line ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES + %% -- ?EXPECTED_SYSTEM_INFO_NAMES2), + %% ?line ExpNs = lists:sort(Ns); + {{3, 0}, _} -> ?line ExpNs = lists:sort(?EXPECTED_SYSTEM_INFO_NAMES - -- ?EXPECTED_SYSTEM_INFO_NAMES2), - ?line ExpNs = lists:sort(Ns); - {{2, 0}, _} -> - ?line [] = Ns + -- ?EXPECTED_SYSTEM_INFO_NAMES3), + ?line ExpNs = lists:sort(Ns) end. chk_sis(SIs, Ns) -> @@ -1188,6 +1203,14 @@ check_si_res(["async_thrs", Value]) -> check_si_res(["sched_thrs", Value]) -> ?line Value = integer_to_list(erlang:system_info(schedulers)); +%% Data added in 3rd version of driver_system_info() (driver version 1.5) +check_si_res(["emu_nif_vsn", _Value]) -> + true; + +%% Data added in 4th version of driver_system_info() (driver version 3.1) +check_si_res(["dirty_sched", _Value]) -> + true; + check_si_res(Unexpected) -> ?line ?t:fail({unexpected_result, Unexpected}). @@ -2369,10 +2392,25 @@ count_proc_sched(Ps, PNs) -> PNs end. +a_test(Config) when is_list(Config) -> + check_io_debug(). + +z_test(Config) when is_list(Config) -> + check_io_debug(). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Utilities %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +check_io_debug() -> + get_stable_check_io_info(), + {NoErrorFds, NoUsedFds, NoDrvSelStructs, NoDrvEvStructs} + = erts_debug:get_internal_state(check_io_debug), + 0 = NoErrorFds, + NoUsedFds = NoDrvSelStructs, + 0 = NoDrvEvStructs, + ok. + %flush_msgs() -> % receive % M -> diff --git a/erts/emulator/test/driver_SUITE_data/sys_info_base_drv.c b/erts/emulator/test/driver_SUITE_data/sys_info_base_drv.c index e44c7dbd5e..964034f5a6 100644 --- a/erts/emulator/test/driver_SUITE_data/sys_info_base_drv.c +++ b/erts/emulator/test/driver_SUITE_data/sys_info_base_drv.c @@ -41,7 +41,9 @@ "thread=%s " \ "smp=%s " \ "async_thrs=%d " \ - "sched_thrs=%d" + "sched_thrs=%d " \ + "emu_nif_vsn=%d.%d" + static size_t sys_info_drv_max_res_len(ErlDrvSysInfo *sip) @@ -55,6 +57,7 @@ sys_info_drv_max_res_len(ErlDrvSysInfo *sip) slen += 5; /* smp */ slen += 20; /* async_thrs */ slen += 20; /* sched_thrs */ + slen += 2*20; /* emu_nif_vsn */ return slen; } @@ -72,7 +75,9 @@ sys_info_drv_sprintf_sys_info(ErlDrvSysInfo *sip, char *str) sip->thread_support ? "true" : "false", sip->smp_support ? "true" : "false", sip->async_threads, - sip->scheduler_threads); + sip->scheduler_threads, + sip->nif_major_version, + sip->nif_minor_version); } #include "sys_info_drv_impl.c" diff --git a/erts/emulator/test/driver_SUITE_data/sys_info_curr_drv.c b/erts/emulator/test/driver_SUITE_data/sys_info_curr_drv.c index 5bbc966932..6d2c47fdaf 100644 --- a/erts/emulator/test/driver_SUITE_data/sys_info_curr_drv.c +++ b/erts/emulator/test/driver_SUITE_data/sys_info_curr_drv.c @@ -40,7 +40,9 @@ "thread=%s " \ "smp=%s " \ "async_thrs=%d " \ - "sched_thrs=%d" + "sched_thrs=%d " \ + "emu_nif_vsn=%d.%d " \ + "dirty_sched=%s" static size_t sys_info_drv_max_res_len(ErlDrvSysInfo *sip) @@ -54,6 +56,8 @@ sys_info_drv_max_res_len(ErlDrvSysInfo *sip) slen += 5; /* smp */ slen += 20; /* async_thrs */ slen += 20; /* sched_thrs */ + slen += 2*20; /* emu_nif_vsn */ + slen += 5; /* dirty_sched */ return slen; } @@ -71,7 +75,10 @@ sys_info_drv_sprintf_sys_info(ErlDrvSysInfo *sip, char *str) sip->thread_support ? "true" : "false", sip->smp_support ? "true" : "false", sip->async_threads, - sip->scheduler_threads); + sip->scheduler_threads, + sip->nif_major_version, + sip->nif_minor_version, + sip->dirty_scheduler_support ? "true" : "false"); } #include "sys_info_drv_impl.c" diff --git a/erts/emulator/test/driver_SUITE_data/sys_info_prev_drv.c b/erts/emulator/test/driver_SUITE_data/sys_info_prev_drv.c index 63c69f751c..2271d7027b 100644 --- a/erts/emulator/test/driver_SUITE_data/sys_info_prev_drv.c +++ b/erts/emulator/test/driver_SUITE_data/sys_info_prev_drv.c @@ -41,7 +41,9 @@ "thread=%s " \ "smp=%s " \ "async_thrs=%d " \ - "sched_thrs=%d" + "sched_thrs=%d " \ + "emu_nif_vsn=%d.%d" + static size_t sys_info_drv_max_res_len(ErlDrvSysInfo *sip) @@ -55,6 +57,7 @@ sys_info_drv_max_res_len(ErlDrvSysInfo *sip) slen += 5; /* smp */ slen += 20; /* async_thrs */ slen += 20; /* sched_thrs */ + slen += 2*20; /* emu_nif_vsn */ return slen; } @@ -72,7 +75,9 @@ sys_info_drv_sprintf_sys_info(ErlDrvSysInfo *sip, char *str) sip->thread_support ? "true" : "false", sip->smp_support ? "true" : "false", sip->async_threads, - sip->scheduler_threads); + sip->scheduler_threads, + sip->nif_major_version, + sip->nif_minor_version); } #include "sys_info_drv_impl.c" diff --git a/erts/emulator/test/float_SUITE_data/fp_drv.c b/erts/emulator/test/float_SUITE_data/fp_drv.c index b80385c3f9..82d18d6440 100644 --- a/erts/emulator/test/float_SUITE_data/fp_drv.c +++ b/erts/emulator/test/float_SUITE_data/fp_drv.c @@ -29,9 +29,14 @@ #if defined (__GNUC__) int _finite(double x); #endif -#ifndef finite -#define finite _finite +#ifndef isfinite +#define isfinite _finite #endif +#elif !defined(HAVE_ISFINITE) && defined(HAVE_FINITE) +/* If not windows and we do not have isfinite */ +#define isfinite finite +#elif !defined(HAVE_ISFINITE) +# error "No finite function found!" #endif #include "erl_driver.h" @@ -79,21 +84,21 @@ do_test(void *unused) x = 3.23e133; y = 3.57e257; z = x*y; - if (finite(z)) + if (isfinite(z)) return "is finite (1)"; x = 5.0; y = 0.0; z = x/y; - if (finite(z)) + if (isfinite(z)) return "is finite (2)"; z = log(-1.0); - if (finite(z)) + if (isfinite(z)) return "is finite (3)"; z = log(0.0); - if (finite(z)) + if (isfinite(z)) return "is finite (4)"; return "ok"; diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index ff5fb8c5af..291c903947 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -1539,13 +1539,21 @@ static ERL_NIF_TERM call_nif_schedule(ErlNifEnv* env, int argc, const ERL_NIF_TE } #ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT + +static int have_dirty_schedulers(void) +{ + ErlNifSysInfo si; + enif_system_info(&si, sizeof(si)); + return si.dirty_scheduler_support; +} + static ERL_NIF_TERM dirty_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int n; char s[10]; ErlNifBinary b; ERL_NIF_TERM result; - if (enif_have_dirty_schedulers()) { + if (have_dirty_schedulers()) { assert(enif_is_on_dirty_scheduler(env)); } assert(argc == 3); @@ -1566,7 +1574,7 @@ static ERL_NIF_TERM call_dirty_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM assert(!enif_is_on_dirty_scheduler(env)); if (argc != 3) return enif_make_badarg(env); - if (enif_have_dirty_schedulers()) { + if (have_dirty_schedulers()) { if (enif_get_int(env, argv[0], &n) && enif_get_string(env, argv[1], s, sizeof s, ERL_NIF_LATIN1) && enif_inspect_binary(env, argv[2], &b)) diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl index e01b2f253b..8792f25d76 100644 --- a/erts/emulator/test/port_SUITE.erl +++ b/erts/emulator/test/port_SUITE.erl @@ -90,6 +90,7 @@ mix_up_ports/1, otp_5112/1, otp_5119/1, otp_6224/1, exit_status_multi_scheduling_block/1, ports/1, spawn_driver/1, spawn_executable/1, close_deaf_port/1, + port_setget_data/1, unregister_name/1, parallelism_option/1]). -export([do_iter_max_ports/2]). @@ -115,6 +116,7 @@ all() -> mix_up_ports, otp_5112, otp_5119, exit_status_multi_scheduling_block, ports, spawn_driver, spawn_executable, close_deaf_port, unregister_name, + port_setget_data, parallelism_option]. groups() -> @@ -2333,6 +2335,55 @@ close_deaf_port_1(N, Cmd) -> {comment, "Could not spawn more than " ++ integer_to_list(N) ++ " OS processes."} end. +%% Test undocumented port_set_data/2 and port_get_data/1 +%% Hammer from multiple processes a while +%% and then abrubtly close the port (OTP-12208). +port_setget_data(Config) when is_list(Config) -> + ok = load_driver(?config(data_dir, Config), "echo_drv"), + Port = erlang:open_port({spawn_driver, "echo_drv"}, []), + + NSched = erlang:system_info(schedulers_online), + PRs = lists:map(fun(I) -> + spawn_opt(fun() -> port_setget_data_hammer(Port,1) end, + [monitor, {scheduler, I rem NSched}]) + end, + lists:seq(1,10)), + receive after 100 -> ok end, + Papa = self(), + lists:foreach(fun({Pid,_}) -> Pid ! {Papa,prepare_for_close} end, PRs), + lists:foreach(fun({Pid,_}) -> + receive {Pid,prepare_for_close} -> ok end + end, + PRs), + port_close(Port), + lists:foreach(fun({Pid,Ref}) -> + receive {'DOWN', Ref, process, Pid, normal} -> ok end + end, + PRs), + ok. + +port_setget_data_hammer(Port, N) -> + Rand = random:uniform(3), + try case Rand of + 1 -> true = erlang:port_set_data(Port, atom); + 2 -> true = erlang:port_set_data(Port, {1,2,3}); + 3 -> erlang:port_get_data(Port) + end + catch + error:badarg -> + true = get(prepare_for_close), + io:format("~p did ~p rounds before port closed\n", [self(), N]), + exit(normal) + end, + receive {Papa, prepare_for_close} -> + put(prepare_for_close, true), + Papa ! {self(),prepare_for_close} + after 0 -> + ok + end, + port_setget_data_hammer(Port, N+1). + + wait_until(Fun) -> case catch Fun() of true -> diff --git a/erts/emulator/test/z_SUITE.erl b/erts/emulator/test/z_SUITE.erl index 4b3075a164..b0c6224dfe 100644 --- a/erts/emulator/test/z_SUITE.erl +++ b/erts/emulator/test/z_SUITE.erl @@ -38,7 +38,7 @@ -export([schedulers_alive/1, node_container_refc_check/1, long_timers/1, pollset_size/1, - check_io_debug/1]). + check_io_debug/1, get_check_io_info/0]). -define(DEFAULT_TIMEOUT, ?t:minutes(5)). @@ -288,11 +288,14 @@ check_io_debug(Config) when is_list(Config) -> end. check_io_debug_test() -> + ?line erlang:display(get_check_io_info()), ?line erts_debug:set_internal_state(available_internal_state, true), - ?line erlang:display(erlang:system_info(check_io)), - ?line NoOfErrorFds = erts_debug:get_internal_state(check_io_debug), + ?line {NoErrorFds, NoUsedFds, NoDrvSelStructs, NoDrvEvStructs} + = erts_debug:get_internal_state(check_io_debug), ?line erts_debug:set_internal_state(available_internal_state, false), - ?line 0 = NoOfErrorFds, + ?line 0 = NoErrorFds, + ?line NoUsedFds = NoDrvSelStructs, + ?line 0 = NoDrvEvStructs, ?line ok. @@ -305,7 +308,7 @@ display_check_io(ChkIo) -> catch erlang:display('--- CHECK IO INFO ---'), catch erlang:display(ChkIo), catch erts_debug:set_internal_state(available_internal_state, true), - NoOfErrorFds = (catch erts_debug:get_internal_state(check_io_debug)), + NoOfErrorFds = (catch element(1, erts_debug:get_internal_state(check_io_debug))), catch erlang:display({'NoOfErrorFds', NoOfErrorFds}), catch erts_debug:set_internal_state(available_internal_state, false), catch erlang:display('--- CHECK IO INFO ---'), @@ -313,14 +316,19 @@ display_check_io(ChkIo) -> get_check_io_info() -> ChkIo = erlang:system_info(check_io), - case lists:keysearch(pending_updates, 1, ChkIo) of - {value, {pending_updates, 0}} -> + PendUpdNo = case lists:keysearch(pending_updates, 1, ChkIo) of + {value, {pending_updates, PendNo}} -> + PendNo; + false -> + 0 + end, + {value, {active_fds, ActFds}} = lists:keysearch(active_fds, 1, ChkIo), + case {PendUpdNo, ActFds} of + {0, 0} -> display_check_io(ChkIo), ChkIo; - false -> - ChkIo; _ -> - receive after 10 -> ok end, + receive after 100 -> ok end, get_check_io_info() end. diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c index 709c6f02d1..5ebde8ca3c 100644 --- a/erts/etc/common/erlexec.c +++ b/erts/etc/common/erlexec.c @@ -128,6 +128,7 @@ static char *pluss_val_switches[] = { "bwt", "cl", "ct", + "ecio", "fwi", "tbt", "wct", diff --git a/erts/etc/unix/etp-commands.in b/erts/etc/unix/etp-commands.in index bf6eb00314..0190ea613e 100644 --- a/erts/etc/unix/etp-commands.in +++ b/erts/etc/unix/etp-commands.in @@ -1130,6 +1130,39 @@ document etp-cp %--------------------------------------------------------------------------- end +define etp-check-beam-ranges + set $etp_ci = 0 + while $etp_ci < 3 + printf "Checking code index %i...\n", $etp_ci + set $etp_j = 0 + while $etp_j < r[$etp_ci].n + set $etp_p = &r[$etp_ci].modules[$etp_j] + if $etp_j > 0 && $etp_p->start < (Range*)$etp_p[-1].end.counter + printf "r[%i].modules[%i]: ERROR start < previous\n", $etp_ci, $etp_j + end + if $etp_p->start > (Range*)$etp_p->end.counter + printf "r[%i].modules[%i]: ERROR start > end\n", $etp_ci, $etp_j + else + if $etp_p->start == (Range*)$etp_p->end.counter + printf "r[%i].modules[%i]: Purged\n", $etp_ci, $etp_j + end + end + set $etp_j = $etp_j + 1 + end + set $etp_ci = $etp_ci + 1 + end +end + +document etp-check-beam-ranges +%--------------------------------------------------------------------------- +% etp-check-beam-ranges +% +% Do consistency check of beam_ranges data structure +% and print errors and empty slots from purged modules. +%--------------------------------------------------------------------------- +end + + ############################################################################ # Commands for special term bunches. # @@ -3552,6 +3585,39 @@ document etp-carrier-blocks %--------------------------------------------------------------------------- end +define etp-address-to-beam-opcode + set $etp_i = 0 + set $etp_min_diff = ((UWord)1 << (sizeof(UWord)*8 - 1)) + set $etp_min_opcode = -1 + set $etp_addr = (UWord) ($arg0) + + while $etp_i < num_instructions && $etp_min_diff > 0 + if ($etp_addr - (UWord)beam_ops[$etp_i]) < $etp_min_diff + set $etp_min_diff = $etp_addr - (UWord)beam_ops[$etp_i] + set $etp_min_opcode = $etp_i + end + set $etp_i = $etp_i + 1 + end + if $etp_min_diff == 0 + printf "Address %p is start of '%s'\n", $etp_addr, opc[$etp_min_opcode].name + else + if $etp_min_opcode >= 0 + printf "Address is %ld bytes into opcode '%s' at %p\n", $etp_min_diff, opc[$etp_min_opcode].name, beam_ops[$etp_min_opcode] + else + printf "Invalid opcode address\n" + end + end +end + +document etp-address-to-beam-opcode +%--------------------------------------------------------------------------- +% Get beam opcode from a native instruction address (within process_main()) +% Arg: Instructon pointer value +% +% Does not work with NO_JUMP_TABLE +%--------------------------------------------------------------------------- +end + ############################################################################ # Toolbox parameter handling diff --git a/erts/etc/win32/Install.c b/erts/etc/win32/Install.c index 500fd166f8..9d85d642ab 100644 --- a/erts/etc/win32/Install.c +++ b/erts/etc/win32/Install.c @@ -80,7 +80,7 @@ int wmain(int argc, wchar_t **argv) } } if (root == NULL) { - if (module = NULL) { + if (module == NULL) { fprintf(stderr, "Cannot GetModuleHandle()\n"); exit(1); } diff --git a/erts/etc/win32/erl.c b/erts/etc/win32/erl.c index 1d116bf36e..772b668586 100644 --- a/erts/etc/win32/erl.c +++ b/erts/etc/win32/erl.c @@ -264,7 +264,7 @@ static void get_parameters(void) int len; - if (module = NULL) { + if (module == NULL) { error("Cannot GetModuleHandle()"); } diff --git a/erts/lib_src/common/erl_misc_utils.c b/erts/lib_src/common/erl_misc_utils.c index d58a28b5cb..7833dd8219 100644 --- a/erts/lib_src/common/erl_misc_utils.c +++ b/erts/lib_src/common/erl_misc_utils.c @@ -1515,7 +1515,7 @@ const char* parse_topology_spec_group(erts_cpu_info_t *cpuinfo, const char* xml, if (is_thread_group) { thread++; } else { - *core_p = (*core_p)++; + *core_p = (*core_p) + 1; } index_procs++; } @@ -1535,9 +1535,9 @@ const char* parse_topology_spec_group(erts_cpu_info_t *cpuinfo, const char* xml, if (parentCacheLevel == 0) { *core_p = 0; - *processor_p = (*processor_p)++; + *processor_p = (*processor_p) + 1; } else { - *core_p = (*core_p)++; + *core_p = (*core_p) + 1; } if (error) diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam Binary files differindex 5f2b619322..001c9c76ed 100644 --- a/erts/preloaded/ebin/erl_prim_loader.beam +++ b/erts/preloaded/ebin/erl_prim_loader.beam diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex cf3effc1e5..1893dd84ac 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam Binary files differindex 7dc7407a81..b467633a4d 100644 --- a/erts/preloaded/ebin/erts_internal.beam +++ b/erts/preloaded/ebin/erts_internal.beam diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam Binary files differindex 5c139c4550..915a2d1aef 100644 --- a/erts/preloaded/ebin/init.beam +++ b/erts/preloaded/ebin/init.beam diff --git a/erts/preloaded/ebin/otp_ring0.beam b/erts/preloaded/ebin/otp_ring0.beam Binary files differindex cf32b79e8d..df1bf932a3 100644 --- a/erts/preloaded/ebin/otp_ring0.beam +++ b/erts/preloaded/ebin/otp_ring0.beam diff --git a/erts/preloaded/ebin/prim_eval.beam b/erts/preloaded/ebin/prim_eval.beam Binary files differindex 37ed8d0365..95b4bbca2d 100644 --- a/erts/preloaded/ebin/prim_eval.beam +++ b/erts/preloaded/ebin/prim_eval.beam diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam Binary files differindex d49578abfa..5e4fc5ba84 100644 --- a/erts/preloaded/ebin/prim_file.beam +++ b/erts/preloaded/ebin/prim_file.beam diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex 8420052533..9d9a4886d9 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam diff --git a/erts/preloaded/ebin/prim_zip.beam b/erts/preloaded/ebin/prim_zip.beam Binary files differindex 8dc8cb961b..d98b0275f4 100644 --- a/erts/preloaded/ebin/prim_zip.beam +++ b/erts/preloaded/ebin/prim_zip.beam diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam Binary files differindex 7507efb076..0744bdb21d 100644 --- a/erts/preloaded/ebin/zlib.beam +++ b/erts/preloaded/ebin/zlib.beam diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl index 466e0b0020..6b86a427ba 100644 --- a/erts/preloaded/src/erl_prim_loader.erl +++ b/erts/preloaded/src/erl_prim_loader.erl @@ -1507,7 +1507,14 @@ real_path(Name,[Path|Paths],Acc,Links) -> [""|_] = LinkPaths -> real_path(Name,LinkPaths++Paths,[],[ThisFile|Links]); LinkPaths -> - real_path(Name,LinkPaths++Paths,Acc,[ThisFile|Links]) + % windows currently does not allow creation of relative symlinks + % across different drives + case erlang:system_info(os_type) of + {win32, _} -> + real_path(Name,LinkPaths++Paths,[],[ThisFile|Links]); + _ -> + real_path(Name,LinkPaths++Paths,Acc,[ThisFile|Links]) + end end; _ -> real_path(Name,Paths,This,Links) diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 98d7a942a6..4ba623d921 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -2233,6 +2233,7 @@ tuple_to_list(_Tuple) -> (dynamic_trace) -> none | dtrace | systemtap; (dynamic_trace_probes) -> boolean(); (elib_malloc) -> false; + (eager_check_io) -> boolean(); (ets_limit) -> pos_integer(); (fullsweep_after) -> {fullsweep_after, non_neg_integer()}; (garbage_collection) -> [{atom(), integer()}]; diff --git a/erts/preloaded/src/erts.app.src b/erts/preloaded/src/erts.app.src index a15da3a421..345a6ae3be 100644 --- a/erts/preloaded/src/erts.app.src +++ b/erts/preloaded/src/erts.app.src @@ -35,7 +35,6 @@ {registered, []}, {applications, []}, {env, []}, - {mod, {erts, []}}, {runtime_dependencies, ["stdlib-2.0", "kernel-3.0", "sasl-2.4"]} ]}. diff --git a/erts/vsn.mk b/erts/vsn.mk index 0db4370ea8..b6a38f9361 100644 --- a/erts/vsn.mk +++ b/erts/vsn.mk @@ -17,7 +17,7 @@ # %CopyrightEnd% # -VSN = 6.1.2 +VSN = 6.2 # Port number 4365 in 4.2 # Port number 4366 in 4.3 diff --git a/lib/asn1/c_src/asn1_erl_nif.c b/lib/asn1/c_src/asn1_erl_nif.c index 53e3aa1678..317a464060 100644 --- a/lib/asn1/c_src/asn1_erl_nif.c +++ b/lib/asn1/c_src/asn1_erl_nif.c @@ -949,7 +949,7 @@ static int ber_decode_value(ErlNifEnv* env, ERL_NIF_TERM *value, unsigned char * } else if (in_buf[*ib_index] == ASN1_INDEFINITE_LENGTH) { (*ib_index)++; curr_head = enif_make_list(env, 0); - if (*ib_index+1 >= in_buf_len) { + if (*ib_index+1 >= in_buf_len || form == ASN1_PRIMITIVE) { return ASN1_INDEF_LEN_ERROR; } while (!(in_buf[*ib_index] == 0 && in_buf[*ib_index + 1] == 0)) { diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml index 11de9ad98f..a7032737bd 100644 --- a/lib/asn1/doc/src/notes.xml +++ b/lib/asn1/doc/src/notes.xml @@ -31,6 +31,30 @@ <p>This document describes the changes made to the asn1 application.</p> +<section><title>Asn1 3.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Several problems where the ASN.1 compiler would crash + when attempting to compile correct specifications have + been corrected.</p> + <p> + Own Id: OTP-12125</p> + </item> + <item> + <p> + Robustness when decoding incorrect BER messages has been + improved.</p> + <p> + Own Id: OTP-12145</p> + </item> + </list> + </section> + +</section> + <section><title>Asn1 3.0.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/asn1/test/asn1_SUITE_data/Constructed.asn b/lib/asn1/test/asn1_SUITE_data/Constructed.asn index 09a66d0c0d..bd49741726 100644 --- a/lib/asn1/test/asn1_SUITE_data/Constructed.asn +++ b/lib/asn1/test/asn1_SUITE_data/Constructed.asn @@ -1,6 +1,3 @@ - - - Constructed DEFINITIONS ::= BEGIN @@ -20,4 +17,7 @@ C ::= CHOICE { S3 ::= SEQUENCE {i INTEGER} S3ext ::= SEQUENCE {i INTEGER, ...} + +OS ::= OCTET STRING + END diff --git a/lib/asn1/test/ber_decode_error.erl b/lib/asn1/test/ber_decode_error.erl index 6fd2450c62..ef11717c45 100644 --- a/lib/asn1/test/ber_decode_error.erl +++ b/lib/asn1/test/ber_decode_error.erl @@ -61,6 +61,10 @@ run([]) -> (catch 'Constructed':decode('S', sub(<<40,16#80,1,1,255,0,0>>, 6))), {error,{asn1,{invalid_length,_}}} = (catch 'Constructed':decode('S', sub(<<40,16#80,1,1,255,0,0>>, 5))), + + %% A primitive must not be encoded with an indefinite length. + {error,{asn1,{invalid_length,_}}} = + (catch 'Constructed':decode('OS', <<4,128,4,3,97,98,99,0,0>>)), ok. sub(Bin, Bytes) -> diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk index 37c843204a..d87c50637d 100644 --- a/lib/asn1/vsn.mk +++ b/lib/asn1/vsn.mk @@ -1,2 +1,2 @@ #next version number to use is 2.0 -ASN1_VSN = 3.0.1 +ASN1_VSN = 3.0.2 diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml index b53ba32e6c..f4ce5369f7 100644 --- a/lib/common_test/doc/src/notes.xml +++ b/lib/common_test/doc/src/notes.xml @@ -32,6 +32,54 @@ <file>notes.xml</file> </header> +<section><title>Common_Test 1.8.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Ticket OTP-11971 introduced a runtime dependency towards + test_server-3.7.1, since the interface between + test_server and common_test was changed. Erroneously, the + common_test.app file was not updated according to this. + This has now been corrected.</p> + <p> + Own Id: OTP-12037</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Warning: this is experimental and may disappear or change + without previous warning.</p> + <p> + Experimental support for running Quickcheck and PropEr + tests from common_test suites is added to common_test. + See the reference manual for the new module + <c>ct_property_testing</c>.</p> + <p> + Experimental property tests are added under + <c>lib/{inet,ssh}/test/property_test</c>. They can be run + directly or from the commont_test suites + <c>inet/ftp_property_test_SUITE.erl</c> and + <c>ssh/test/ssh_property_test_SUITE.erl</c>.</p> + <p> + See the code in the <c>test</c> directories and the man + page for details.</p> + <p> + (Thanks to Tuncer Ayaz for a patch adding Triq)</p> + <p> + Own Id: OTP-12119</p> + </item> + </list> + </section> + +</section> + <section><title>Common_Test 1.8.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/common_test/src/ct_cover.erl b/lib/common_test/src/ct_cover.erl index cf2860ae25..c7f446dee9 100644 --- a/lib/common_test/src/ct_cover.erl +++ b/lib/common_test/src/ct_cover.erl @@ -128,20 +128,20 @@ get_spec(File) -> catch get_spec_test(File). get_spec_test(File) -> - FullName = filename:absname(File), - case filelib:is_file(FullName) of + Dir = filename:dirname(File), % always abs path in here, set in ct_run + case filelib:is_file(File) of true -> - case file:consult(FullName) of + case file:consult(File) of {ok,Terms} -> Import = case lists:keysearch(import, 1, Terms) of {value,{_,Imps=[S|_]}} when is_list(S) -> ImpsFN = lists:map(fun(F) -> - filename:absname(F) + filename:absname(F,Dir) end, Imps), test_files(ImpsFN, ImpsFN); {value,{_,Imp=[IC|_]}} when is_integer(IC) -> - ImpFN = filename:absname(Imp), + ImpFN = filename:absname(Imp,Dir), test_files([ImpFN], [ImpFN]); _ -> [] @@ -149,9 +149,9 @@ get_spec_test(File) -> Export = case lists:keysearch(export, 1, Terms) of {value,{_,Exp=[EC|_]}} when is_integer(EC) -> - filename:absname(Exp); + filename:absname(Exp,Dir); {value,{_,[Exp]}} -> - filename:absname(Exp); + filename:absname(Exp,Dir); _ -> undefined end, @@ -179,7 +179,7 @@ get_spec_test(File) -> E; [CoverSpec] -> CoverSpec1 = remove_excludes_and_dups(CoverSpec), - {FullName,Nodes,Import,Export,CoverSpec1}; + {File,Nodes,Import,Export,CoverSpec1}; _ -> {error,multiple_apps_in_cover_spec} end; @@ -190,7 +190,7 @@ get_spec_test(File) -> {error,{invalid_cover_spec,Error}} end; false -> - {error,{cant_read_cover_spec_file,FullName}} + {error,{cant_read_cover_spec_file,File}} end. collect_apps([{level,Level}|Ts], Apps) -> diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 43eabb18d5..7037cdca73 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -129,7 +129,13 @@ datestr_from_dirname([]) -> close(Info, StartDir) -> %% close executes on the ct_util process, not on the logger process %% so we need to use a local copy of the log cache data - LogCacheBin = make_last_run_index(), + LogCacheBin = + case make_last_run_index() of + {error,_} -> % log server not responding + undefined; + LCB -> + LCB + end, put(ct_log_cache,LogCacheBin), Cache2File = fun() -> case get(ct_log_cache) of @@ -710,6 +716,7 @@ logger_loop(State) -> end end, if Importance >= (100-VLvl) -> + CtLogFd = State#logger_state.ct_log_fd, case get_groupleader(Pid, GL, State) of {tc_log,TCGL,TCGLs} -> case erlang:is_process_alive(TCGL) of @@ -723,14 +730,15 @@ logger_loop(State) -> %% Group leader is dead, so write to the %% CtLog or unexpected_io log instead unexpected_io(Pid,Category,Importance, - List,State), + List,CtLogFd), + logger_loop(State) end; {ct_log,_Fd,TCGLs} -> %% If category is ct_internal then write %% to ct_log, else write to unexpected_io %% log - unexpected_io(Pid,Category,Importance,List,State), + unexpected_io(Pid,Category,Importance,List,CtLogFd), logger_loop(State#logger_state{ tc_groupleaders = TCGLs}) end; @@ -803,16 +811,15 @@ logger_loop(State) -> ok end. -create_io_fun(FromPid, State) -> +create_io_fun(FromPid, CtLogFd) -> %% we have to build one io-list of all strings %% before printing, or other io printouts (made in %% parallel) may get printed between this header %% and footer - Fd = State#logger_state.ct_log_fd, fun({Str,Args}, IoList) -> case catch io_lib:format(Str,Args) of {'EXIT',_Reason} -> - io:format(Fd, "Logging fails! Str: ~p, Args: ~p~n", + io:format(CtLogFd, "Logging fails! Str: ~p, Args: ~p~n", [Str,Args]), %% stop the testcase, we need to see the fault exit(FromPid, {log_printout_error,Str,Args}), @@ -827,28 +834,53 @@ create_io_fun(FromPid, State) -> print_to_log(sync, FromPid, Category, TCGL, List, State) -> %% in some situations (exceptions), the printout is made from the %% test server IO process and there's no valid group leader to send to + CtLogFd = State#logger_state.ct_log_fd, if FromPid /= TCGL -> - IoFun = create_io_fun(FromPid, State), + IoFun = create_io_fun(FromPid, CtLogFd), io:format(TCGL,"~ts", [lists:foldl(IoFun, [], List)]); true -> - unexpected_io(FromPid,Category,?MAX_IMPORTANCE,List,State) + unexpected_io(FromPid,Category,?MAX_IMPORTANCE,List,CtLogFd) end, State; print_to_log(async, FromPid, Category, TCGL, List, State) -> %% in some situations (exceptions), the printout is made from the %% test server IO process and there's no valid group leader to send to + CtLogFd = State#logger_state.ct_log_fd, Printer = if FromPid /= TCGL -> - IoFun = create_io_fun(FromPid, State), + IoFun = create_io_fun(FromPid, CtLogFd), fun() -> test_server:permit_io(TCGL, self()), - io:format(TCGL, "~ts", [lists:foldl(IoFun, [], List)]) + + %% Since asynchronous io gets can get buffered if + %% the file system is slow, there is also a risk that + %% the group leader has terminated before we get to + %% the io:format(GL, ...) call. We check this and + %% print "expired" messages to the unexpected io + %% log instead (best we can do). + + case erlang:is_process_alive(TCGL) of + true -> + try io:format(TCGL, "~ts", + [lists:foldl(IoFun,[],List)]) of + _ -> ok + catch + _:terminated -> + unexpected_io(FromPid, Category, + ?MAX_IMPORTANCE, + List, CtLogFd) + end; + false -> + unexpected_io(FromPid, Category, + ?MAX_IMPORTANCE, + List, CtLogFd) + end end; true -> fun() -> - unexpected_io(FromPid,Category,?MAX_IMPORTANCE, - List,State) + unexpected_io(FromPid, Category, ?MAX_IMPORTANCE, + List, CtLogFd) end end, case State#logger_state.async_print_jobs of @@ -3149,12 +3181,11 @@ html_encoding(latin1) -> html_encoding(utf8) -> "utf-8". -unexpected_io(Pid,ct_internal,_Importance,List,State) -> - IoFun = create_io_fun(Pid,State), - io:format(State#logger_state.ct_log_fd, "~ts", - [lists:foldl(IoFun, [], List)]); -unexpected_io(Pid,_Category,_Importance,List,State) -> - IoFun = create_io_fun(Pid,State), +unexpected_io(Pid,ct_internal,_Importance,List,CtLogFd) -> + IoFun = create_io_fun(Pid,CtLogFd), + io:format(CtLogFd, "~ts", [lists:foldl(IoFun, [], List)]); +unexpected_io(Pid,_Category,_Importance,List,CtLogFd) -> + IoFun = create_io_fun(Pid,CtLogFd), Data = io_lib:format("~ts", [lists:foldl(IoFun, [], List)]), test_server_io:print_unexpected(Data), ok. diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl index 39d089f04c..52acda5388 100644 --- a/lib/common_test/src/ct_property_test.erl +++ b/lib/common_test/src/ct_property_test.erl @@ -59,8 +59,10 @@ %%% ''' %%% %%% <warning> +%%% <p> %%% This is experimental code which may be changed or removed -%%% anytime without any warning. +%%% anytime without any warning. +%%% </p> %%% </warning> %%% %%% @end diff --git a/lib/common_test/test/ct_cover_SUITE.erl b/lib/common_test/test/ct_cover_SUITE.erl index 47080b5577..87ba4ae1b9 100644 --- a/lib/common_test/test/ct_cover_SUITE.erl +++ b/lib/common_test/test/ct_cover_SUITE.erl @@ -76,7 +76,8 @@ all() -> cover_node_option, ct_cover_add_remove_nodes, otp_9956, - cross + cross, + export_import ]. %%-------------------------------------------------------------------- @@ -199,6 +200,20 @@ cross(Config) -> ok. +export_import(Config) -> + DataDir = ?config(data_dir,Config), + false = check_cover(Config), + CoverSpec1 = + default_cover_file_content() ++ [{export,"export_import.coverdata"}], + CoverFile1 = create_cover_file(export_import1,CoverSpec1,Config), + {ok,Events1} = run_test(export_import1,default,[{cover,CoverFile1}],Config), + check_calls(Events1,1), + CoverSpec2 = + default_cover_file_content() ++ [{import,"export_import.coverdata"}], + CoverFile2 = create_cover_file(export_import2,CoverSpec2,Config), + {ok,Events2} = run_test(export_import2,default,[{cover,CoverFile2}],Config), + check_calls(Events2,2), + ok. %%%----------------------------------------------------------------- %%% HELP FUNCTIONS diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 2e2b45d59f..746469584d 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -481,6 +481,7 @@ er_loop(Evs) -> From ! {event_receiver,lists:reverse(Evs)}, er_loop(Evs); stop -> + unregister(event_receiver), ok end. diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index 55e9661d7d..d48a0a5599 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -31,6 +31,22 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 5.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Corrected a bug with incorrect code generation when + inlining was turned on.</p> + <p> + Own Id: OTP-12132</p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 5.0.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index 0a86352f40..d042596557 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 5.0.1 +COMPILER_VSN = 5.0.2 diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 7712173ed8..98384978a5 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -693,7 +693,7 @@ <p>Decrypts <c>CipherText</c> according to the stream cipher <c>Type</c> specified in stream_init/3. <c>PlainText</c> can be any number of bytes. The initial <c>State</c> is created using <seealso marker="#stream_init-2">stream_init</seealso>. - <c>NewState</c> must be passed into the next call to <c>stream_encrypt</c>.</p> + <c>NewState</c> must be passed into the next call to <c>stream_decrypt</c>.</p> </desc> </func> diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml index 1bd2034b93..82b6de9acd 100644 --- a/lib/crypto/doc/src/notes.xml +++ b/lib/crypto/doc/src/notes.xml @@ -30,6 +30,23 @@ </header> <p>This document describes the changes made to the Crypto application.</p> +<section><title>Crypto 3.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Make <c>crypto</c> verify major version number of OpenSSL + header files and runtime library. Loading of + <c>crypto</c> will fail if there is a version mismatch.</p> + <p> + Own Id: OTP-12146 Aux Id: seq12700 </p> + </item> + </list> + </section> + +</section> + <section><title>Crypto 3.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk index b2bb1d7dfb..2a7f3c4558 100644 --- a/lib/crypto/vsn.mk +++ b/lib/crypto/vsn.mk @@ -1 +1 @@ -CRYPTO_VSN = 3.4 +CRYPTO_VSN = 3.4.1 diff --git a/lib/debugger/src/Makefile b/lib/debugger/src/Makefile index 90189dd297..d61519f1ad 100644 --- a/lib/debugger/src/Makefile +++ b/lib/debugger/src/Makefile @@ -63,7 +63,7 @@ MODULES= \ HRL_FILES= -INTERNAL_HRL_FILES= dbg_ieval.hrl +INTERNAL_HRL_FILES= dbg_ieval.hrl dbg_wx_filedialog_win.hrl ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/debugger/src/dbg_icmd.erl b/lib/debugger/src/dbg_icmd.erl index b1bf4ebecc..ce12c1beb3 100644 --- a/lib/debugger/src/dbg_icmd.erl +++ b/lib/debugger/src/dbg_icmd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2013. All Rights Reserved. +%% Copyright Ericsson AB 1998-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -49,10 +49,6 @@ %% specifies if the process should break. %%-------------------------------------------------------------------- -%% Common Test adaptation -cmd({call_remote,0,ct_line,line,_As}, Bs, _Ieval) -> - Bs; - cmd(Expr, Bs, Ieval) -> cmd(Expr, Bs, get(next_break), Ieval). diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl index 77297de0f3..96f9f91808 100644 --- a/lib/debugger/src/dbg_ieval.erl +++ b/lib/debugger/src/dbg_ieval.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2013. All Rights Reserved. +%% Copyright Ericsson AB 1998-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -457,11 +457,6 @@ do_eval_function(Mod, Fun, As0, Bs0, _, Ieval0) when is_function(Fun); exception(error, Reason, Bs0, Ieval0) end; -%% Common Test adaptation -do_eval_function(ct_line, line, As, Bs, extern, #ieval{level=Le}=Ieval) -> - debugged_cmd({apply,ct_line,line,As}, Bs, Ieval#ieval{level=Le+1}), - {value, ignore, Bs}; - do_eval_function(Mod, Name, As0, Bs0, Called, Ieval0) -> #ieval{level=Le,line=Li,top=Top} = Ieval0, trace(call, {Called, {Le,Li,Mod,Name,As0}}), @@ -896,11 +891,6 @@ expr({make_ext_fun,Line,MFA0}, Bs0, Ieval0) -> exception(error, badarg, Bs, Ieval, true) end; -%% Common test adaptation -expr({call_remote,0,ct_line,line,As0,Lc}, Bs0, Ieval0) -> - {As,_Bs} = eval_list(As0, Bs0, Ieval0), - eval_function(ct_line, line, As, Bs0, extern, Ieval0, Lc); - %% Local function call expr({local_call,Line,F,As0,Lc}, Bs0, #ieval{module=M} = Ieval0) -> Ieval = Ieval0#ieval{line=Line}, diff --git a/lib/debugger/src/int.erl b/lib/debugger/src/int.erl index 2755db64b8..908390ce50 100644 --- a/lib/debugger/src/int.erl +++ b/lib/debugger/src/int.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2013. All Rights Reserved. +%% Copyright Ericsson AB 1998-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -265,9 +265,6 @@ first_lines(Clauses) -> first_line({clause,_L,_Vars,_,Exprs}) -> first_line(Exprs); -%% Common Test adaptation -first_line([{call_remote,0,ct_line,line,_As}|Exprs]) -> - first_line(Exprs); first_line([Expr|_Exprs]) -> % Expr = {Op, Line, ..varying no of args..} element(2, Expr). diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml index 4e26a9e95e..e482b1e6f8 100644 --- a/lib/dialyzer/doc/src/dialyzer.xml +++ b/lib/dialyzer/doc/src/dialyzer.xml @@ -238,7 +238,10 @@ <item>Include warnings for functions that only return by means of an exception.</item> <tag><c><![CDATA[-Wrace_conditions]]></c>***</tag> - <item>Include warnings for possible race conditions.</item> + <item>Include warnings for possible race conditions. Note that the + analysis that finds data races performs intra-procedural data flow analysis + and can sometimes explode in time. Enable it at your own risk. + </item> <tag><c><![CDATA[-Wunderspecs]]></c>***</tag> <item>Warn about underspecified functions (the -spec is strictly more allowing than the success typing).</item> diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml index bdd9c61c5c..d35639aa32 100644 --- a/lib/dialyzer/doc/src/notes.xml +++ b/lib/dialyzer/doc/src/notes.xml @@ -31,6 +31,36 @@ <p>This document describes the changes made to the Dialyzer application.</p> +<section><title>Dialyzer 2.7.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> A bug concerning <c>is_record/2,3</c> has been fixed, + as well as some cases where Dialyzer could crash due to + reaching system limits. </p> + <p> + Own Id: OTP-12018</p> + </item> + <item> + <p> When given the <c>-Wunderspecs</c> flag Dialyzer + sometimes output bogus warnings for parametrized types. + This bug has been fixed. </p> + <p> + Own Id: OTP-12111</p> + </item> + <item> + <p>Dialyzer now fetch the compile options from beam + files, and use them when creating core from the abstract + code. Previously the options were ignored. </p> + <p> + Own Id: OTP-12150</p> + </item> + </list> + </section> + +</section> + <section><title>Dialyzer 2.7.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index 4e2ec67b35..5297a3a7b4 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -98,7 +98,7 @@ get_abstract_code_from_src(File) -> {'ok', abstract_code()} | {'error', [string()]}. get_abstract_code_from_src(File, Opts) -> - case compile:file(File, [to_pp, binary|Opts]) of + case compile:noenv_file(File, [to_pp, binary|Opts]) of error -> {error, []}; {error, Errors, _} -> {error, format_errors(Errors)}; {ok, _, AbstrCode} -> {ok, AbstrCode} @@ -173,7 +173,7 @@ get_core_from_abstract_code(AbstrCode, Opts) -> AbstrCode1 = cleanup_parse_transforms(AbstrCode), %% Remove parse_transforms (and other options) from compile options. Opts2 = cleanup_compile_options(Opts), - try compile:forms(AbstrCode1, Opts2 ++ src_compiler_opts()) of + try compile:noenv_forms(AbstrCode1, Opts2 ++ src_compiler_opts()) of {ok, _, Core} -> {ok, Core}; _What -> error catch @@ -444,21 +444,18 @@ cleanup_parse_transforms([]) -> -spec cleanup_compile_options([compile:option()]) -> [compile:option()]. +cleanup_compile_options(Opts) -> + lists:filter(fun keep_compile_option/1, Opts). + %% Using abstract, not asm or core. -cleanup_compile_options([from_asm|Opts]) -> - Opts; -cleanup_compile_options([asm|Opts]) -> - Opts; -cleanup_compile_options([from_core|Opts]) -> - Opts; -%% The parse transform will already have been applied, may cause problems if it -%% is re-applied. -cleanup_compile_options([{parse_transform, _}|Opts]) -> - Opts; -cleanup_compile_options([Other|Opts]) -> - [Other|cleanup_compile_options(Opts)]; -cleanup_compile_options([]) -> - []. +keep_compile_option(from_asm) -> false; +keep_compile_option(asm) -> false; +keep_compile_option(from_core) -> false; +%% The parse transform will already have been applied, may cause +%% problems if it is re-applied. +keep_compile_option({parse_transform, _}) -> false; +keep_compile_option(warnings_as_errors) -> false; +keep_compile_option(_) -> true. -spec format_errors([{module(), string()}]) -> [string()]. diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index b0cb3ec4f9..58cc77c2fa 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 2.7.1 +DIALYZER_VSN = 2.7.2 diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index d89e1dfd26..7f69bdbfbf 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -42,6 +42,59 @@ first.</p> <!-- ===================================================================== --> +<section><title>diameter 1.7.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Don't leave extra bit in decoded AVP data.</p> + <p> + An extra bit could be communicated in the data field of a + diameter_avp record in the case of length errors. Of no + consequence for code using the record encoding of + Diameter messages, but code examining diameter_avp + records would see this bit.</p> + <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> + Own Id: OTP-12074</p> + </item> + <item> + <p> + Fix counting of outgoing requests and answers setting the + E-bit.</p> + <p> + OTP-11721 broke these counters for all outgoing requests + except DWR, and caused answers setting the E-bit to be + counted as unknown messages.</p> + <p> + Own Id: OTP-12080</p> + </item> + <item> + <p> + Fix Failed-AVP decode.</p> + <p> + The best-effort decode only worked for AVPs in the common + dictionary, not for those in the dictionary of the + application identified in the Diameter Header of the + answer message in question.</p> + <p> + Failed-AVP in an answer decoded with the RFC 3588 common + dictionary (diameter_gen_base_rfc3588) was regarded as an + error. The RFC 6733 dictionary was unaffected.</p> + <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> + Own Id: OTP-12094</p> + </item> + </list> + </section> + +</section> + <section><title>diameter 1.7</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -114,6 +167,9 @@ first.</p> M-bit, resulting in 5001) failed if the AVP contained a complete header.</p> <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> Own Id: OTP-11946</p> </item> <item> @@ -152,6 +208,9 @@ first.</p> otherwise not. AVPs of type Grouped are decoded as much as possible, as deeply as possible.</p> <p> + Dictionary files must be recompiled for the fix to have + effect.</p> + <p> Own Id: OTP-11936 Aux Id: OTP-11007 </p> </item> <item> diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 5a068c1a25..d91a776321 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -132,7 +132,7 @@ gen(parse, ParseD, _Mod) -> [?VERSION | ParseD]; gen(forms, ParseD, Mod) -> - pp(erl_forms(Mod, ParseD)); + preprocess(Mod, erl_forms(Mod, ParseD)); gen(hrl, ParseD, Mod) -> gen_hrl(Mod, ParseD); @@ -838,19 +838,19 @@ rec_name(Name, Prefix) -> Prefix ++ Name. %% =========================================================================== -%% pp/1 +%% preprocess/2 %% %% Preprocess forms as generated by 'forms' option. In particular, %% replace the include_lib attributes in generated forms by the %% corresponding forms, extracting the latter from an existing %% dictionary (diameter_gen_relay). The resulting forms can be %% compiled to beam using compile:forms/2 (which does no preprocessing -%% or it's own; DiY currently appears to be the only way to preprocess +%% of it's own; DiY currently appears to be the only way to preprocess %% a forms list). -pp(Forms) -> +preprocess(Mod, Forms) -> {_, Beam, _} = code:get_object_code(diameter_gen_relay), - pp(Forms, abstract_code(Beam)). + pp(Forms, remod(Mod, abstract_code(Beam))). pp(Forms, {ok, Code}) -> Files = files(Code, []), @@ -859,6 +859,25 @@ pp(Forms, {ok, Code}) -> pp(Forms, {error, Reason}) -> erlang:error({forms, Reason, Forms}). +%% Replace literal diameter_gen_relay atoms in the extracted forms. +%% ?MODULE for example. + +remod(Mod, L) + when is_list(L) -> + [remod(Mod, T) || T <- L]; + +remod(Mod, {atom, _, diameter_gen_relay} = T) -> + setelement(3, T, Mod); + +remod(Mod, T) + when is_tuple(T) -> + list_to_tuple(remod(Mod, tuple_to_list(T))); + +remod(_, T) -> + T. + +%% Replace include_lib by the corresponding forms. + include({attribute, _, include_lib, Path}, Files) -> Inc = filename:basename(Path), [{Inc, Forms}] = [T || {F, _} = T <- Files, F == Inc], %% expect one @@ -867,6 +886,8 @@ include({attribute, _, include_lib, Path}, Files) -> include(T, _) -> [T]. +%% Extract abstract code. + abstract_code(Beam) -> case beam_lib:chunks(Beam, [abstract_code]) of {ok, {_Mod, [{abstract_code, {_Vsn, Code}}]}} -> @@ -877,6 +898,8 @@ abstract_code(Beam) -> {E, Reason} end. +%% Extract filename/forms pairs for included forms. + files([{attribute, _, file, {Path, _}} | T], Acc) -> {Body, Rest} = lists:splitwith(fun({attribute, _, file, _}) -> false; (_) -> true diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml index b3440ce6e1..52b7529f70 100644 --- a/lib/edoc/doc/src/notes.xml +++ b/lib/edoc/doc/src/notes.xml @@ -31,6 +31,22 @@ <p>This document describes the changes made to the EDoc application.</p> +<section><title>Edoc 0.7.15</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix spec to doc generation from erl_docgen and edoc for + maps</p> + <p> + Own Id: OTP-12058</p> + </item> + </list> + </section> + +</section> + <section><title>Edoc 0.7.14</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl index f4e78e8f3a..a102d432bc 100644 --- a/lib/edoc/src/edoc_layout.erl +++ b/lib/edoc/src/edoc_layout.erl @@ -831,8 +831,6 @@ t_type([#xmlElement{name = nonempty_list, content = Es}]) -> t_nonempty_list(Es); t_type([#xmlElement{name = map, content = Es}]) -> t_map(Es); -t_type([#xmlElement{name = map_field, content=Es}]) -> - t_map_field(Es); t_type([#xmlElement{name = tuple, content = Es}]) -> t_tuple(Es); t_type([#xmlElement{name = 'fun', content = Es}]) -> @@ -882,9 +880,10 @@ t_fun(Es) -> [") -> "] ++ t_utype(get_elem(type, Es))). t_map(Es) -> - ["#{"] ++ seq(fun t_utype_elem/1, Es, ["}"]). + Fs = get_elem(map_field, Es), + ["#{"] ++ seq(fun t_map_field/1, Fs, ["}"]). -t_map_field([K,V]) -> +t_map_field(#xmlElement{content = [K,V]}) -> t_utype_elem(K) ++ [" => "] ++ t_utype_elem(V). t_record(E, Es) -> @@ -1084,8 +1083,6 @@ ot_type([#xmlElement{name = tuple, content = Es}]) -> ot_tuple(Es); ot_type([#xmlElement{name = map, content = Es}]) -> ot_map(Es); -ot_type([#xmlElement{name = map_field, content = Es}]) -> - ot_map_field(Es); ot_type([#xmlElement{name = 'fun', content = Es}]) -> ot_fun(Es); ot_type([#xmlElement{name = record, content = Es}]) -> @@ -1143,10 +1140,10 @@ ot_tuple(Es) -> {type,0,tuple,[ot_utype_elem(E) || E <- Es]}. ot_map(Es) -> - {type,0,map,[ot_utype_elem(E) || E <- Es]}. + {type,0,map,[ot_map_field(E) || E <- get_elem(map_field,Es)]}. -ot_map_field(Es) -> - {type,0,map_field_assoc,[ot_utype_elem(E) || E <- Es]}. +ot_map_field(#xmlElement{content=[K,V]}) -> + {type,0,map_field_assoc,ot_utype_elem(K), ot_utype_elem(V)}. ot_fun(Es) -> Range = ot_utype(get_elem(type, Es)), diff --git a/lib/edoc/src/edoc_types.erl b/lib/edoc/src/edoc_types.erl index d4e00d3ecd..8a6c8eb33e 100644 --- a/lib/edoc/src/edoc_types.erl +++ b/lib/edoc/src/edoc_types.erl @@ -143,7 +143,7 @@ to_xml(#t_fun{args = As, range = T}, Env) -> {'fun', [{argtypes, map(fun wrap_utype/2, As, Env)}, wrap_utype(T, Env)]}; to_xml(#t_map{ types = Ts}, Env) -> - {map, map(fun wrap_utype/2, Ts, Env)}; + {map, map(fun to_xml/2, Ts, Env)}; to_xml(#t_map_field{ k_type=K, v_type=V}, Env) -> {map_field, [wrap_utype(K,Env), wrap_utype(V, Env)]}; to_xml(#t_tuple{types = Ts}, Env) -> diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk index 281a792118..b1cf115b29 100644 --- a/lib/edoc/vsn.mk +++ b/lib/edoc/vsn.mk @@ -1 +1 @@ -EDOC_VSN = 0.7.14 +EDOC_VSN = 0.7.15 diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml index 8009a8d6a3..4417551aa8 100644 --- a/lib/eldap/doc/src/eldap.xml +++ b/lib/eldap/doc/src/eldap.xml @@ -48,7 +48,7 @@ scope() See baseObject/0, singleLevel/0, wholeSubtree/0 dereference() See neverDerefAliases/0, derefInSearching/0, derefFindingBaseObj/0, derefAlways/0 filter() See present/1, substrings/2, equalityMatch/2, greaterOrEqual/2, lessOrEqual/2, - approxMatch/2, + approxMatch/2, extensibleMatch/2, 'and'/1, 'or'/1, 'not'/1. </pre> <p></p> @@ -75,7 +75,9 @@ filter() See present/1, substrings/2, <p>Setup a connection to an LDAP server, the <c>HOST</c>'s are tried in order.</p> <p>The log function takes three arguments, <c>fun(Level, FormatString, [FormatArg]) end</c>.</p> <p>Timeout set the maximum time in milliseconds that each server request may take.</p> - <p>Currently, the only TCP socket option accepted is <c>inet6</c>. Default is <c>inet</c>.</p> + <p>All TCP socket options are accepted except + <c>active</c>, <c>binary</c>, <c>deliver</c>, <c>list</c>, <c>mode</c> and <c>packet</c> + </p> </desc> </func> <func> @@ -346,6 +348,16 @@ filter() See present/1, substrings/2, <desc> <p>Create a approximation match filter.</p> </desc> </func> <func> + <name>extensibleMatch(MatchValue, OptionalAttrs) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>MatchValue = string()</v> + <v>OptionalAttrs = [Attr]</v> + <v>Attr = {matchingRule,string()} | {type,string()} | {dnAttributes,boolean()}</v> + </type> + <desc> <p>Creates an extensible match filter. For example, <c>eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseExactMatch"}]))</c> creates a filter which performs a <c>caseExactMatch</c> on the attribute <c>sn</c> and matches with the value <c>"Bar"</c>. The default value of <c>dnAttributes</c> is <c>false</c>.</p> </desc> + </func> + <func> <name>'and'([Filter]) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml index 089bb731d4..f92d100757 100644 --- a/lib/eldap/doc/src/notes.xml +++ b/lib/eldap/doc/src/notes.xml @@ -30,7 +30,36 @@ </header> <p>This document describes the changes made to the Eldap application.</p> - <section><title>Eldap 1.0.3</title> + <section><title>Eldap 1.0.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + <c>eldap:open/2</c> and <c>eldap:open/3</c> gave wrong + return values for option errors.</p> + <p> + Own Id: OTP-12182</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Nearly all TCP options are possible to give in the + <c>eldap:open/2</c> call.</p> + <p> + Own Id: OTP-12171</p> + </item> + </list> + </section> + +</section> + +<section><title>Eldap 1.0.3</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl index 1cd328cde3..3fa440d37d 100644 --- a/lib/eldap/src/eldap.erl +++ b/lib/eldap/src/eldap.erl @@ -12,8 +12,10 @@ -vc('$Id$ '). -export([open/1,open/2,simple_bind/3,controlling_process/2, start_tls/2, start_tls/3, + getopts/2, baseObject/0,singleLevel/0,wholeSubtree/0,close/1, equalityMatch/2,greaterOrEqual/2,lessOrEqual/2, + extensibleMatch/2, approxMatch/2,search/2,substrings/2,present/1, 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2, mod_replace/2, add/3, delete/2, modify_dn/5,parse_dn/1, @@ -92,6 +94,15 @@ start_tls(Handle, TlsOptions, Timeout) -> recv(Handle). %%% -------------------------------------------------------------------- +%%% Ask for option values on the socket. +%%% Warning: This is an undocumented function for testing purposes only. +%%% Use at own risk... +%%% -------------------------------------------------------------------- +getopts(Handle, OptNames) when is_pid(Handle), is_list(OptNames) -> + send(Handle, {getopts, OptNames}), + recv(Handle). + +%%% -------------------------------------------------------------------- %%% Shutdown connection (and process) asynchronous. %%% -------------------------------------------------------------------- @@ -340,6 +351,27 @@ substrings(Type, SubStr) when is_list(Type), is_list(SubStr) -> {substrings,#'SubstringFilter'{type = Type, substrings = Ss}}. +%%% +%%% Filter for extensibleMatch +%%% +extensibleMatch(MatchValue, OptArgs) -> + MatchingRuleAssertion = + mra(OptArgs, #'MatchingRuleAssertion'{matchValue = MatchValue}), + {extensibleMatch, MatchingRuleAssertion}. + +mra([{matchingRule,Val}|T], Ack) when is_list(Val) -> + mra(T, Ack#'MatchingRuleAssertion'{matchingRule=Val}); +mra([{type,Val}|T], Ack) when is_list(Val) -> + mra(T, Ack#'MatchingRuleAssertion'{type=Val}); +mra([{dnAttributes,true}|T], Ack) -> + mra(T, Ack#'MatchingRuleAssertion'{dnAttributes="TRUE"}); +mra([{dnAttributes,false}|T], Ack) -> + mra(T, Ack#'MatchingRuleAssertion'{dnAttributes="FALSE"}); +mra([H|_], _) -> + throw({error,{extensibleMatch_arg,H}}); +mra([], Ack) -> + Ack. + %%% -------------------------------------------------------------------- %%% Worker process. We keep track of a controlling process to %%% be able to terminate together with it. @@ -374,24 +406,35 @@ parse_args([{sslopts, Opts}|T], Cpid, Data) when is_list(Opts) -> parse_args([{sslopts, _}|T], Cpid, Data) -> parse_args(T, Cpid, Data); parse_args([{tcpopts, Opts}|T], Cpid, Data) when is_list(Opts) -> - parse_args(T, Cpid, Data#eldap{tcp_opts = inet6_opt(Opts) ++ Data#eldap.tcp_opts}); + parse_args(T, Cpid, Data#eldap{tcp_opts = tcp_opts(Opts,Cpid,Data#eldap.tcp_opts)}); parse_args([{log, F}|T], Cpid, Data) when is_function(F) -> parse_args(T, Cpid, Data#eldap{log = F}); parse_args([{log, _}|T], Cpid, Data) -> parse_args(T, Cpid, Data); parse_args([H|_], Cpid, _) -> send(Cpid, {error,{wrong_option,H}}), + unlink(Cpid), exit(wrong_option); parse_args([], _, Data) -> Data. -inet6_opt(Opts) -> - case proplists:get_value(inet6, Opts) of +tcp_opts([Opt|Opts], Cpid, Acc) -> + Key = if is_atom(Opt) -> Opt; + is_tuple(Opt) -> element(1,Opt) + end, + case lists:member(Key,[active,binary,deliver,list,mode,packet]) of + false -> + tcp_opts(Opts, Cpid, [Opt|Acc]); true -> - [inet6]; - _ -> - [] - end. + tcp_opts_error(Opt, Cpid) + end; +tcp_opts([], _Cpid, Acc) -> Acc. + +tcp_opts_error(Opt, Cpid) -> + send(Cpid, {error, {{forbidden_tcp_option,Opt}, + "This option affects the eldap functionality and can't be set by user"}}), + unlink(Cpid), + exit(forbidden_tcp_option). %%% Try to connect to the hosts in the listed order, %%% and stop with the first one to which a successful @@ -466,6 +509,36 @@ loop(Cpid, Data) -> unlink(Cpid), exit(closed); + {From, {getopts, OptNames}} -> + Result = + try + [case OptName of + port -> {port, Data#eldap.port}; + log -> {log, Data#eldap.log}; + timeout -> {timeout, Data#eldap.timeout}; + ssl -> {ssl, Data#eldap.ldaps}; + {sslopts, SslOptNames} when Data#eldap.using_tls==true -> + case ssl:getopts(Data#eldap.fd, SslOptNames) of + {ok,SslOptVals} -> {sslopts, SslOptVals}; + {error,Reason} -> throw({error,Reason}) + end; + {sslopts, _} -> + throw({error,no_tls}); + {tcpopts, TcpOptNames} -> + case inet:getopts(Data#eldap.fd, TcpOptNames) of + {ok,TcpOptVals} -> {tcpopts, TcpOptVals}; + {error,Posix} -> throw({error,Posix}) + end + end || OptName <- OptNames] + of + OptsList -> {ok,OptsList} + catch + throw:Error -> Error; + Class:Error -> {error,{Class,Error}} + end, + send(From, Result), + ?MODULE:loop(Cpid, Data); + {Cpid, 'EXIT', Reason} -> ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]), exit(Reason); @@ -811,6 +884,7 @@ v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV}; v_filter({approxMatch,AV}) -> {approxMatch,AV}; v_filter({present,A}) -> {present,A}; v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S}; +v_filter({extensibleMatch,S}) when is_record(S,'MatchingRuleAssertion') -> {extensibleMatch,S}; v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}). v_modifications(Mods) -> diff --git a/lib/eldap/test/Makefile b/lib/eldap/test/Makefile index 3c5810eece..24e71cebaa 100644 --- a/lib/eldap/test/Makefile +++ b/lib/eldap/test/Makefile @@ -28,6 +28,7 @@ INCLUDES= -I. -I ../include # ---------------------------------------------------- MODULES= \ + eldap_connections_SUITE \ eldap_basic_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl index 6c3d303da0..d87f3ac4ac 100644 --- a/lib/eldap/test/eldap_basic_SUITE.erl +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -106,7 +106,9 @@ api(doc) -> "Basic test that all api functions works as expected"; api(suite) -> []; api(Config) -> {Host,Port} = proplists:get_value(ldap_server, Config), - {ok, H} = eldap:open([Host], [{port,Port}]), + {ok, H} = eldap:open([Host], [{port,Port} + ,{log,fun(Lvl,Fmt,Args)-> io:format("~p: ~s",[Lvl,io_lib:format(Fmt,Args)]) end} + ]), %% {ok, H} = eldap:open([Host], [{port,Port+1}, {ssl, true}]), do_api_checks(H, Config), eldap:close(H), @@ -232,6 +234,12 @@ chk_search(H, BasePath) -> {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(F_AND), F_NOT = eldap:'and'([eldap:present("objectclass"), eldap:'not'(eldap:present("ou"))]), {ok, #eldap_search_result{entries=[#eldap_entry{}, #eldap_entry{}]}} = Search(F_NOT), + {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])), + {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"2.5.13.5"}])), + {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])), + {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])), + {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"gluffgluff"}])), + {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])), {ok,FB}. %% FIXME chk_modify(H, FB) -> diff --git a/lib/eldap/test/eldap_connections_SUITE.erl b/lib/eldap/test/eldap_connections_SUITE.erl new file mode 100644 index 0000000000..c5460fef09 --- /dev/null +++ b/lib/eldap/test/eldap_connections_SUITE.erl @@ -0,0 +1,147 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012-2014. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(eldap_connections_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +%-include_lib("eldap/include/eldap.hrl"). + + +all() -> + [ + {group, v4}, + {group, v6} + ]. + + +init_per_group(v4, Config) -> + [{listen_opts, []}, + {listen_host, "localhost"}, + {connect_opts, []} + | Config]; +init_per_group(v6, Config) -> + {ok, Hostname} = inet:gethostname(), + case lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts,[])) of + true -> + [{listen_opts, [inet6]}, + {listen_host, "::"}, + {connect_opts, [{tcpopts,[inet6]}]} + | Config]; + false -> + {skip, io_lib:format("~p is not an ipv6_host",[Hostname])} + end. + + +end_per_group(_GroupName, Config) -> + Config. + + +groups() -> + [{v4, [], [tcp_connection, tcp_connection_option]}, + {v6, [], [tcp_connection, tcp_connection_option]} + ]. + + +init_per_suite(Config) -> Config. + + +end_per_suite(_Config) -> ok. + + +init_per_testcase(_TestCase, Config) -> + case gen_tcp:listen(0, proplists:get_value(listen_opts,Config)) of + {ok,LSock} -> + {ok,{_,Port}} = inet:sockname(LSock), + [{listen_socket,LSock}, + {listen_port,Port} + | Config]; + Other -> + {fail, Other} + end. + + +end_per_testcase(_TestCase, Config) -> + catch gen_tcp:close( proplists:get_value(listen_socket, Config) ). + +%%%================================================================ +%%% +%%% Test cases +%%% +%%%---------------------------------------------------------------- +tcp_connection(Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(listen_port, Config), + Opts = proplists:get_value(connect_opts, Config), + case eldap:open([Host], [{port,Port}|Opts]) of + {ok,_H} -> + Sl = proplists:get_value(listen_socket, Config), + case gen_tcp:accept(Sl,1000) of + {ok,_S} -> ok; + {error,timeout} -> ct:fail("server side accept timeout",[]) + end; + Other -> ct:fail("eldap:open failed: ~p",[Other]) + end. + + +%%%---------------------------------------------------------------- +tcp_connection_option(Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(listen_port, Config), + Opts = proplists:get_value(connect_opts, Config), + Sl = proplists:get_value(listen_socket, Config), + + %% Make an option value to test. The option must be implemented on all + %% platforms that we test on. Must check what the default value is + %% so we don't happen to choose that particular value. + {ok,[{linger,DefaultLinger}]} = inet:getopts(Sl, [linger]), + TestLinger = case DefaultLinger of + {false,_} -> {true,5}; + {true,_} -> {false,0} + end, + + case catch eldap:open([Host], + [{port,Port},{tcpopts,[{linger,TestLinger}]}|Opts]) of + {ok,H} -> + case gen_tcp:accept(Sl,1000) of + {ok,_} -> + case eldap:getopts(H, [{tcpopts,[linger]}]) of + {ok,[{tcpopts,[{linger,ActualLinger}]}]} -> + case ActualLinger of + TestLinger -> + ok; + DefaultLinger -> + ct:fail("eldap:getopts: 'linger' didn't change," + " got ~p (=default) expected ~p", + [ActualLinger,TestLinger]); + _ -> + ct:fail("eldap:getopts: bad 'linger', got ~p expected ~p", + [ActualLinger,TestLinger]) + end; + Other -> + ct:fail("eldap:getopts: bad result ~p",[Other]) + end; + {error,timeout} -> + ct:fail("server side accept timeout",[]) + end; + + Other -> + ct:fail("eldap:open failed: ~p",[Other]) + end. diff --git a/lib/eldap/vsn.mk b/lib/eldap/vsn.mk index efdc30b476..432ba2e742 100644 --- a/lib/eldap/vsn.mk +++ b/lib/eldap/vsn.mk @@ -1,2 +1 @@ -ELDAP_VSN = 1.0.3 - +ELDAP_VSN = 1.1 diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml index e546eb97fc..f194fb6d6c 100644 --- a/lib/erl_docgen/doc/src/notes.xml +++ b/lib/erl_docgen/doc/src/notes.xml @@ -30,7 +30,23 @@ </header> <p>This document describes the changes made to the <em>erl_docgen</em> application.</p> - <section><title>Erl_Docgen 0.3.5</title> + <section><title>Erl_Docgen 0.3.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix spec to doc generation from erl_docgen and edoc for + maps</p> + <p> + Own Id: OTP-12058</p> + </item> + </list> + </section> + +</section> + +<section><title>Erl_Docgen 0.3.5</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/erl_docgen/src/docgen_otp_specs.erl b/lib/erl_docgen/src/docgen_otp_specs.erl index cbdbbbee80..1075c47801 100644 --- a/lib/erl_docgen/src/docgen_otp_specs.erl +++ b/lib/erl_docgen/src/docgen_otp_specs.erl @@ -390,8 +390,6 @@ t_type([#xmlElement{name = tuple, content = Es}]) -> t_tuple(Es); t_type([#xmlElement{name = map, content = Es}]) -> t_map(Es); -t_type([#xmlElement{name = map_field, content = Es}]) -> - t_map_field(Es); t_type([#xmlElement{name = 'fun', content = Es}]) -> ["fun("] ++ t_fun(Es) ++ [")"]; t_type([E = #xmlElement{name = record, content = Es}]) -> @@ -435,9 +433,10 @@ t_tuple(Es) -> ["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]). t_map(Es) -> - ["#{"] ++ seq(fun t_utype_elem/1, Es, ["}"]). + Fs = get_elem(map_field, Es), + ["#{"] ++ seq(fun t_map_field/1, Fs, ["}"]). -t_map_field([K,V]) -> +t_map_field(#xmlElement{content = [K,V]}) -> [t_utype_elem(K) ++ " => " ++ t_utype_elem(V)]. t_fun(Es) -> @@ -557,14 +556,12 @@ ot_type([#xmlElement{name = tuple, content = Es}]) -> ot_tuple(Es); ot_type([#xmlElement{name = map, content = Es}]) -> ot_map(Es); -ot_type([#xmlElement{name = map_field, content = Es}]) -> - ot_map_field(Es); ot_type([#xmlElement{name = 'fun', content = Es}]) -> ot_fun(Es); ot_type([#xmlElement{name = record, content = Es}]) -> ot_record(Es); ot_type([#xmlElement{name = abstype, content = Es}]) -> - ot_abstype(Es); + ot_abstype(Es); ot_type([#xmlElement{name = union, content = Es}]) -> ot_union(Es). @@ -616,10 +613,10 @@ ot_tuple(Es) -> {type,0,tuple,[ot_utype_elem(E) || E <- Es]}. ot_map(Es) -> - {type,0,map,[ot_utype_elem(E) || E <- Es]}. + {type,0,map,[ot_map_field(E) || E <- get_elem(map_field,Es)]}. -ot_map_field(Es) -> - {type,0,map_field_assoc,[ot_utype_elem(E) || E <- Es]}. +ot_map_field(#xmlElement{content=[K,V]}) -> + {type,0,map_field_assoc, ot_utype_elem(K), ot_utype_elem(V)}. ot_fun(Es) -> Range = ot_utype(get_elem(type, Es)), diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk index 0f89922275..8bfcc08698 100644 --- a/lib/erl_docgen/vsn.mk +++ b/lib/erl_docgen/vsn.mk @@ -1 +1 @@ -ERL_DOCGEN_VSN = 0.3.5 +ERL_DOCGEN_VSN = 0.3.6 diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml index 3f85af8956..a055e854e3 100644 --- a/lib/erl_interface/doc/src/notes.xml +++ b/lib/erl_interface/doc/src/notes.xml @@ -30,6 +30,38 @@ </header> <p>This document describes the changes made to the Erl_interface application.</p> +<section><title>Erl_Interface 3.7.19</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Added a <c>.app</c> file for the application.</p> + <p> + Own Id: OTP-12178</p> + </item> + </list> + </section> + +</section> + +<section><title>Erl_Interface 3.7.18</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Implement --enable-sanitizers[=sanitizers]. Similar to + debugging with Valgrind, it's very useful to enable + -fsanitize= switches to catch bugs at runtime.</p> + <p> + Own Id: OTP-12153</p> + </item> + </list> + </section> + +</section> + <section><title>Erl_Interface 3.7.17</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/erl_interface/ebin/.gitignore b/lib/erl_interface/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/erl_interface/ebin/.gitignore diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in index e36b39c1fb..7d914a02ca 100644 --- a/lib/erl_interface/src/Makefile.in +++ b/lib/erl_interface/src/Makefile.in @@ -40,6 +40,13 @@ include $(TARGET)/eidefs.mk include $(ERL_TOP)/make/output.mk +EBINDIR=../ebin + +APP_FILE= erl_interface.app + +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBINDIR)/$(APP_FILE) + USING_MINGW=@MIXED_CYGWIN_MINGW@ USING_MSYS_VC==@MIXED_MSYS_VC@ USING_CYGWIN_VC==@MIXED_MSYS_VC@ @@ -212,7 +219,8 @@ ifeq ($(USING_VC),yes) TARGETS = \ $(OBJ_TARGETS) \ - $(EXE_TARGETS) + $(EXE_TARGETS) \ + $(APP_TARGET) OBJ_TARGETS = \ $(MT_EILIB) \ @@ -241,7 +249,8 @@ else ifeq ($USING_MINGW,yes) TARGETS = \ $(OBJ_TARGETS) \ - $(EXE_TARGETS) + $(EXE_TARGETS) \ + $(APP_TARGET) OBJ_TARGETS = \ $(MD_EILIB) \ @@ -259,7 +268,8 @@ ifdef THR_DEFS TARGETS = \ $(OBJ_TARGETS) \ - $(EXE_TARGETS) + $(EXE_TARGETS) \ + $(APP_TARGET) OBJ_TARGETS = \ $(ST_EILIB) \ @@ -281,7 +291,8 @@ else TARGETS = \ $(OBJ_TARGETS) \ - $(EXE_TARGETS) + $(EXE_TARGETS) \ + $(APP_TARGET) OBJ_TARGETS = \ $(ST_EILIB) \ @@ -597,12 +608,15 @@ $(MDD_OBJDIR)/%.o: %.c # Create directories ########################################################################### -_create_dirs := $(shell mkdir -p $(BINDIR) $(OBJDIR) $(ST_OBJDIR) $(MT_OBJDIR) $(MD_OBJDIR) $(MDD_OBJDIR)) +_create_dirs := $(shell mkdir -p $(EBINDIR) $(BINDIR) $(OBJDIR) $(ST_OBJDIR) $(MT_OBJDIR) $(MD_OBJDIR) $(MDD_OBJDIR)) ########################################################################### # Special rules ########################################################################### +$(APP_TARGET): $(APP_SRC) ../vsn.mk + $(vsn_verbose)sed -e 's;%VSN%;$(ERL_INTERFACE_VSN);' $< > $@ + ifeq ($(TARGET),win32) # Windows archive creation @@ -857,6 +871,7 @@ release: opt $(INSTALL_DIR) "$(RELSYSDIR)/include" $(INSTALL_DIR) "$(RELSYSDIR)/lib" $(INSTALL_DIR) "$(RELSYSDIR)/bin" + $(INSTALL_DIR) "$(RELSYSDIR)/ebin" $(INSTALL_DIR) "$(RELSYSDIR)/src/auxdir" $(INSTALL_DIR) "$(RELSYSDIR)/src/connect" $(INSTALL_DIR) "$(RELSYSDIR)/src/decode" @@ -868,6 +883,7 @@ release: opt $(INSTALL_DIR) "$(RELSYSDIR)/src/registry" $(INSTALL_DIR) "$(RELEASE_PATH)/usr/include" $(INSTALL_DIR) "$(RELEASE_PATH)/usr/lib" + $(INSTALL_DATA) $(APP_TARGET) "$(RELSYSDIR)/ebin/$(APP_FILE)" $(INSTALL_DATA) $(HEADERS) "$(RELSYSDIR)/include" $(INSTALL_DATA) $(HEADERS) "$(RELEASE_PATH)/usr/include" $(INSTALL_DATA) $(OBJ_TARGETS) "$(RELSYSDIR)/lib" diff --git a/lib/erl_interface/src/decode/decode_big.c b/lib/erl_interface/src/decode/decode_big.c index b54ac85be2..b87d97d634 100644 --- a/lib/erl_interface/src/decode/decode_big.c +++ b/lib/erl_interface/src/decode/decode_big.c @@ -151,13 +151,18 @@ int ei_big_comp(erlang_big *x, erlang_big *y) #endif #ifdef USE_ISINF_ISNAN /* simulate finite() */ -# define finite(f) (!isinf(f) && !isnan(f)) -# define HAVE_FINITE +# define isfinite(f) (!isinf(f) && !isnan(f)) +# define HAVE_ISFINITE +#elif defined(isfinite) && !defined(HAVE_ISFINITE) +# define HAVE_ISFINITE +#elif !defined(HAVE_ISFINITE) && defined(HAVE_FINITE) +# define isfinite finite +# define HAVE_ISFINITE #endif #ifdef NO_FPE_SIGNALS # define ERTS_FP_CHECK_INIT() do {} while (0) -# define ERTS_FP_ERROR(f, Action) if (!finite(f)) { Action; } else {} +# define ERTS_FP_ERROR(f, Action) if (!isfinite(f)) { Action; } else {} # define ERTS_SAVE_FP_EXCEPTION() # define ERTS_RESTORE_FP_EXCEPTION() #else diff --git a/lib/erl_interface/src/erl_interface.app.src b/lib/erl_interface/src/erl_interface.app.src new file mode 100644 index 0000000000..11f884c36b --- /dev/null +++ b/lib/erl_interface/src/erl_interface.app.src @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% This is an -*- erlang -*- file. +%% + +{application, erl_interface, + [ + {description, "Erl Interface"}, + {vsn, "%VSN%"}, + {modules, []}, + {registered, []}, + {applications, []}, + {env, []}, + {runtime_dependencies, []} + ] +}. diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk index b1e612a9eb..e39aa4f514 100644 --- a/lib/erl_interface/vsn.mk +++ b/lib/erl_interface/vsn.mk @@ -1,2 +1,2 @@ -EI_VSN = 3.7.17 +EI_VSN = 3.7.19 ERL_INTERFACE_VSN = $(EI_VSN) diff --git a/lib/eunit/doc/src/notes.xml b/lib/eunit/doc/src/notes.xml index 72ffda3cd5..e5a190e3e9 100644 --- a/lib/eunit/doc/src/notes.xml +++ b/lib/eunit/doc/src/notes.xml @@ -32,6 +32,21 @@ </header> <p>This document describes the changes made to the EUnit application.</p> +<section><title>Eunit 2.2.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Minor refactoring.</p> + <p> + Own Id: OTP-12051</p> + </item> + </list> + </section> + +</section> + <section><title>Eunit 2.2.7</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/eunit/src/Makefile b/lib/eunit/src/Makefile index e6dab67363..47aef104ff 100644 --- a/lib/eunit/src/Makefile +++ b/lib/eunit/src/Makefile @@ -46,6 +46,8 @@ SOURCES= \ INCLUDE_FILES = eunit.hrl +INTERNAL_HRL_FILES= eunit_internal.hrl + PARSE_TRANSFORM_BIN = $(PARSE_TRANSFORM:%.erl=$(EBIN)/%.$(EMULATOR)) TARGET_FILES= $(SOURCES:%.erl=$(EBIN)/%.$(EMULATOR)) @@ -78,7 +80,7 @@ all: $(OBJECTS) clean: - rm -f $(OBJECTS) + rm -f $(OBJECTS) $(PARSE_TRANSFORM_BIN) rm -f core *~ distclean: clean @@ -119,6 +121,7 @@ release_spec: opt $(INSTALL_DATA) $(PARSE_TRANSFORM_BIN) $(OBJECTS) "$(RELSYSDIR)/ebin" $(INSTALL_DIR) "$(RELSYSDIR)/src" $(INSTALL_DATA) $(PARSE_TRANSFORM) $(SOURCES) "$(RELSYSDIR)/src" + $(INSTALL_DATA) $(INTERNAL_HRL_FILES) "$(RELSYSDIR)/src" $(INSTALL_DIR) "$(RELSYSDIR)/include" $(INSTALL_DATA) $(INCLUDE_DELIVERABLES) "$(RELSYSDIR)/include" diff --git a/lib/eunit/vsn.mk b/lib/eunit/vsn.mk index f04c0536fe..855e1dac88 100644 --- a/lib/eunit/vsn.mk +++ b/lib/eunit/vsn.mk @@ -1 +1 @@ -EUNIT_VSN = 2.2.7 +EUNIT_VSN = 2.2.8 diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml index e8552eabcc..2962e4a9ac 100644 --- a/lib/hipe/doc/src/notes.xml +++ b/lib/hipe/doc/src/notes.xml @@ -30,6 +30,28 @@ </header> <p>This document describes the changes made to HiPE.</p> +<section><title>Hipe 3.11.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> The pretty-printing of bitstrings has been corrected. + </p> + <p> + Own Id: OTP-12015</p> + </item> + <item> + <p> A bug concerning <c>is_record/2,3</c> has been fixed, + as well as some cases where Dialyzer could crash due to + reaching system limits. </p> + <p> + Own Id: OTP-12018</p> + </item> + </list> + </section> + +</section> + <section><title>Hipe 3.11</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk index c30695d4f0..cf1976d8d6 100644 --- a/lib/hipe/vsn.mk +++ b/lib/hipe/vsn.mk @@ -1 +1 @@ -HIPE_VSN = 3.11 +HIPE_VSN = 3.11.1 diff --git a/lib/ic/doc/src/notes.xml b/lib/ic/doc/src/notes.xml index 03af316f75..bacac09f11 100644 --- a/lib/ic/doc/src/notes.xml +++ b/lib/ic/doc/src/notes.xml @@ -30,7 +30,22 @@ <file>notes.xml</file> </header> - <section><title>IC 4.3.5</title> + <section><title>IC 4.3.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix compiler warnings reported by LLVM</p> + <p> + Own Id: OTP-12138</p> + </item> + </list> + </section> + +</section> + +<section><title>IC 4.3.5</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/ic/vsn.mk b/lib/ic/vsn.mk index 2ffbbad444..bb273c7b57 100644 --- a/lib/ic/vsn.mk +++ b/lib/ic/vsn.mk @@ -1 +1 @@ -IC_VSN = 4.3.5 +IC_VSN = 4.3.6 diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index 06cb035370..4178cb7d4c 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -302,7 +302,7 @@ filename() = string() will be sent to that process: <c>{http, {RequestId, stream_start, Headers}}, {http, {RequestId, stream, BinBodyPart}}, {http, {RequestId, stream_end, Headers}}</c>. When - streaming to to the calling processes using the option + streaming to the calling processes using the option <c>{self, once}</c> the first message will have an additional element e.i. <c>{http, {RequestId, stream_start, Headers, Pid}}</c>, this is the process id that should be used as an argument to diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index d586536b0a..921de8e490 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -32,7 +32,58 @@ <file>notes.xml</file> </header> - <section><title>Inets 5.10.2</title> + <section><title>Inets 5.10.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix some spelling mistakes in documentation</p> + <p> + Own Id: OTP-12152</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + httpd: Seperate timeout for TLS/SSL handshake from + keepalive timeout</p> + <p> + Own Id: OTP-12013</p> + </item> + <item> + <p> + Warning: this is experimental and may disappear or change + without previous warning.</p> + <p> + Experimental support for running Quickcheck and PropEr + tests from common_test suites is added to common_test. + See the reference manual for the new module + <c>ct_property_testing</c>.</p> + <p> + Experimental property tests are added under + <c>lib/{inet,ssh}/test/property_test</c>. They can be run + directly or from the commont_test suites + <c>inet/ftp_property_test_SUITE.erl</c> and + <c>ssh/test/ssh_property_test_SUITE.erl</c>.</p> + <p> + See the code in the <c>test</c> directories and the man + page for details.</p> + <p> + (Thanks to Tuncer Ayaz for a patch adding Triq)</p> + <p> + Own Id: OTP-12119</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 5.10.2</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index 5674599ac5..8e51b1be5a 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -60,6 +60,7 @@ -define(DATA_ACCEPT_TIMEOUT, infinity). -define(DEFAULT_MODE, passive). -define(PROGRESS_DEFAULT, ignore). +-define(FTP_EXT_DEFAULT, false). %% Internal Constants -define(FTP_PORT, 21). @@ -94,7 +95,8 @@ ipfamily, % inet | inet6 | inet6fb4 progress = ignore, % ignore | pid() dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity - tls_upgrading_data_connection = false + tls_upgrading_data_connection = false, + ftp_extension = ?FTP_EXT_DEFAULT }). @@ -969,6 +971,8 @@ start_options(Options) -> %% timeout %% dtimeout %% progress +%% ftp_extension + open_options(Options) -> ?fcrt("open_options", [{options, Options}]), ValidateMode = @@ -1013,6 +1017,11 @@ open_options(Options) -> (_) -> false end, + ValidateFtpExtension = + fun(true) -> true; + (false) -> true; + (_) -> false + end, ValidOptions = [{mode, ValidateMode, false, ?DEFAULT_MODE}, {host, ValidateHost, true, ehost}, @@ -1020,7 +1029,8 @@ open_options(Options) -> {ipfamily, ValidateIpFamily, false, inet}, {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT}, {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT}, - {progress, ValidateProgress, false, ?PROGRESS_DEFAULT}], + {progress, ValidateProgress, false, ?PROGRESS_DEFAULT}, + {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}], validate_options(Options, ValidOptions, []). tls_options(Options) -> @@ -1174,12 +1184,14 @@ handle_call({_, {open, ip_comm, Opts}}, From, State) -> DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), Progress = key_search(progress, Opts, ignore), IpFamily = key_search(ipfamily, Opts, inet), + FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), State2 = State#state{client = From, mode = Mode, progress = progress(Progress), ipfamily = IpFamily, - dtimeout = DTimeout}, + dtimeout = DTimeout, + ftp_extension = FtpExt}, ?fcrd("handle_call(open) -> setup ctrl connection with", [{host, Host}, {port, Port}, {timeout, Timeout}]), @@ -1202,11 +1214,13 @@ handle_call({_, {open, ip_comm, Host, Opts}}, From, State) -> Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), Progress = key_search(progress, Opts, ignore), + FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), State2 = State#state{client = From, mode = Mode, progress = progress(Progress), - dtimeout = DTimeout}, + dtimeout = DTimeout, + ftp_extension = FtpExt}, case setup_ctrl_connection(Host, Port, Timeout, State2) of {ok, State3, WaitTimeout} -> @@ -1785,7 +1799,8 @@ handle_ctrl_result({pos_compl, Lines}, ipfamily = inet, client = From, caller = {setup_data_connection, Caller}, - timeout = Timeout} = State) -> + timeout = Timeout, + ftp_extension = false} = State) -> {_, [?LEFT_PAREN | Rest]} = lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines), @@ -1806,6 +1821,28 @@ handle_ctrl_result({pos_compl, Lines}, {noreply,State#state{client = undefined, caller = undefined}} end; +handle_ctrl_result({pos_compl, Lines}, + #state{mode = passive, + ipfamily = inet, + client = From, + caller = {setup_data_connection, Caller}, + csock = CSock, + timeout = Timeout, + ftp_extension = true} = State) -> + + [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), + {ok, {IP, _}} = peername(CSock), + + ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]), + case connect(IP, list_to_integer(PortStr), Timeout, State) of + {ok, _, Socket} -> + handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); + {error, _Reason} = Error -> + gen_server:reply(From, Error), + {noreply, State#state{client = undefined, caller = undefined}} + end; + + %% FTP server does not support passive mode: try to fallback on active mode handle_ctrl_result(_, #state{mode = passive, @@ -2157,7 +2194,8 @@ setup_ctrl_connection(Host, Port, Timeout, State) -> setup_data_connection(#state{mode = active, caller = Caller, - csock = CSock} = State) -> + csock = CSock, + ftp_extension = FtpExt} = State) -> case (catch sockname(CSock)) of {ok, {{_, _, _, _, _, _, _, _} = IP, _}} -> {ok, LSock} = @@ -2174,11 +2212,18 @@ setup_data_connection(#state{mode = active, {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, binary, {packet, 0}]), {ok, Port} = inet:port(LSock), - {IP1, IP2, IP3, IP4} = IP, - {Port1, Port2} = {Port div 256, Port rem 256}, - send_ctrl_message(State, - mk_cmd("PORT ~w,~w,~w,~w,~w,~w", - [IP1, IP2, IP3, IP4, Port1, Port2])), + case FtpExt of + false -> + {IP1, IP2, IP3, IP4} = IP, + {Port1, Port2} = {Port div 256, Port rem 256}, + send_ctrl_message(State, + mk_cmd("PORT ~w,~w,~w,~w,~w,~w", + [IP1, IP2, IP3, IP4, Port1, Port2])); + true -> + IpAddress = inet_parse:ntoa(IP), + Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]), + send_ctrl_message(State, Cmd) + end, activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, {LSock, Caller}}}} @@ -2191,9 +2236,17 @@ setup_data_connection(#state{mode = passive, ipfamily = inet6, {noreply, State#state{caller = {setup_data_connection, Caller}}}; setup_data_connection(#state{mode = passive, ipfamily = inet, - caller = Caller} = State) -> + caller = Caller, + ftp_extension = false} = State) -> send_ctrl_message(State, mk_cmd("PASV", [])), activate_ctrl_connection(State), + {noreply, State#state{caller = {setup_data_connection, Caller}}}; + +setup_data_connection(#state{mode = passive, ipfamily = inet, + caller = Caller, + ftp_extension = true} = State) -> + send_ctrl_message(State, mk_cmd("EPSV", [])), + activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, Caller}}}. connect(Host, Port, Timeout, #state{ipfamily = inet = IpFam}) -> diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index d152d9f0be..0bbd40d656 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -350,7 +350,7 @@ handle_call(#request{address = Addr} = Request, _, {reply, ok, State0#state{keep_alive = NewKeepAlive, session = NewSession}}; undefined -> - %% Note: tcp-message reciving has already been + %% Note: tcp-message receiving has already been %% activated by handle_pipeline/2. ?hcrd("no current request", []), cancel_timer(Timers#timers.queue_timer, @@ -632,7 +632,7 @@ handle_info({timeout, RequestId}, handle_info(timeout_queue, State = #state{request = undefined}) -> {stop, normal, State}; -%% Timing was such as the pipeline_timout was not canceled! +%% Timing was such as the queue_timeout was not canceled! handle_info(timeout_queue, #state{timers = Timers} = State) -> {noreply, State#state{timers = Timers#timers{queue_timer = undefined}}}; diff --git a/lib/jinterface/doc/src/notes.xml b/lib/jinterface/doc/src/notes.xml index e81a9f82d2..46d89f0cdb 100644 --- a/lib/jinterface/doc/src/notes.xml +++ b/lib/jinterface/doc/src/notes.xml @@ -30,6 +30,56 @@ </header> <p>This document describes the changes made to the Jinterface application.</p> +<section><title>Jinterface 1.5.11</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Added a <c>.app</c> file for the application.</p> + <p> + Own Id: OTP-12178</p> + </item> + </list> + </section> + +</section> + +<section><title>Jinterface 1.5.10</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Array now show meaningful values in exceptions.</p> + <p> + Own Id: OTP-12049</p> + </item> + <item> + <p> + Documentation improvements.</p> + <p> + Own Id: OTP-12050</p> + </item> + <item> + <p> + Include the cause when raising a new IOException, which + should make the reason for the exception clearer.</p> + <p> + Own Id: OTP-12075</p> + </item> + <item> + <p> + Arrays (here: md5 and freeVars) must not be compared with + equals, which is broken (compares identity).</p> + <p> + Own Id: OTP-12121</p> + </item> + </list> + </section> + +</section> + <section><title>Jinterface 1.5.9</title> <section><title>Improvements and New Features</title> diff --git a/lib/jinterface/ebin/.gitignore b/lib/jinterface/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/jinterface/ebin/.gitignore diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java index 85d303689f..b8a973753a 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java @@ -20,7 +20,6 @@ package com.ericsson.otp.erlang; import java.io.IOException; -import java.io.InputStream; import java.net.Socket; import java.util.Random; @@ -87,7 +86,7 @@ public abstract class AbstractConnection extends Thread { protected boolean connected = false; // connection status protected Socket socket; // communication channel protected OtpPeer peer; // who are we connected to - protected OtpLocalNode self; // this nodes id + protected OtpLocalNode localNode; // this nodes id String name; // local name of this connection protected boolean cookieOk = false; // already checked the cookie for this @@ -137,7 +136,7 @@ public abstract class AbstractConnection extends Thread { */ protected AbstractConnection(final OtpLocalNode self, final Socket s) throws IOException, OtpAuthException { - this.self = self; + this.localNode = self; peer = new OtpPeer(); socket = s; @@ -181,7 +180,7 @@ public abstract class AbstractConnection extends Thread { protected AbstractConnection(final OtpLocalNode self, final OtpPeer other) throws IOException, OtpAuthException { peer = other; - this.self = self; + this.localNode = self; socket = null; int port; @@ -234,6 +233,7 @@ public abstract class AbstractConnection extends Thread { if (!connected) { throw new IOException("Not connected"); } + @SuppressWarnings("resource") final OtpOutputStream header = new OtpOutputStream(headerLen); // preamble: 4 byte length + "passthrough" tag + version @@ -246,7 +246,7 @@ public abstract class AbstractConnection extends Thread { header.write_long(regSendTag); header.write_any(from); if (sendCookie) { - header.write_atom(self.cookie()); + header.write_atom(localNode.cookie()); } else { header.write_atom(""); } @@ -278,6 +278,7 @@ public abstract class AbstractConnection extends Thread { if (!connected) { throw new IOException("Not connected"); } + @SuppressWarnings("resource") final OtpOutputStream header = new OtpOutputStream(headerLen); // preamble: 4 byte length + "passthrough" tag + version @@ -289,7 +290,7 @@ public abstract class AbstractConnection extends Thread { header.write_tuple_head(3); header.write_long(sendTag); if (sendCookie) { - header.write_atom(self.cookie()); + header.write_atom(localNode.cookie()); } else { header.write_atom(""); } @@ -312,6 +313,7 @@ public abstract class AbstractConnection extends Thread { private void cookieError(final OtpLocalNode local, final OtpErlangAtom cookie) throws OtpAuthException { try { + @SuppressWarnings("resource") final OtpOutputStream header = new OtpOutputStream(headerLen); // preamble: 4 byte length + "passthrough" tag + version @@ -347,6 +349,7 @@ public abstract class AbstractConnection extends Thread { msg[0] = new OtpErlangAtom("$gen_cast"); msg[1] = new OtpErlangTuple(msgbody); + @SuppressWarnings("resource") final OtpOutputStream payload = new OtpOutputStream( new OtpErlangTuple(msg)); @@ -384,6 +387,7 @@ public abstract class AbstractConnection extends Thread { if (!connected) { throw new IOException("Not connected"); } + @SuppressWarnings("resource") final OtpOutputStream header = new OtpOutputStream(headerLen); // preamble: 4 byte length + "passthrough" tag @@ -420,6 +424,7 @@ public abstract class AbstractConnection extends Thread { if (!connected) { throw new IOException("Not connected"); } + @SuppressWarnings("resource") final OtpOutputStream header = new OtpOutputStream(headerLen); // preamble: 4 byte length + "passthrough" tag @@ -468,6 +473,7 @@ public abstract class AbstractConnection extends Thread { if (!connected) { throw new IOException("Not connected"); } + @SuppressWarnings("resource") final OtpOutputStream header = new OtpOutputStream(headerLen); // preamble: 4 byte length + "passthrough" tag @@ -488,6 +494,7 @@ public abstract class AbstractConnection extends Thread { do_send(header); } + @SuppressWarnings("resource") @Override public void run() { if (!connected) { @@ -506,7 +513,7 @@ public abstract class AbstractConnection extends Thread { // don't return until we get a real message // or a failure of some kind (e.g. EXIT) // read length and read buffer must be atomic! - tick_loop: do { + do { // read 4 bytes - get length of incoming packet // socket.getInputStream().read(lbuf); readSock(socket, lbuf); @@ -526,6 +533,7 @@ public abstract class AbstractConnection extends Thread { final byte[] tmpbuf = new byte[len]; // i = socket.getInputStream().read(tmpbuf); readSock(socket, tmpbuf); + ibuf.close(); ibuf = new OtpInputStream(tmpbuf, flags); if (ibuf.read1() != passThrough) { @@ -567,12 +575,12 @@ public abstract class AbstractConnection extends Thread { } cookie = (OtpErlangAtom) head.elementAt(1); if (sendCookie) { - if (!cookie.atomValue().equals(self.cookie())) { - cookieError(self, cookie); + if (!cookie.atomValue().equals(localNode.cookie())) { + cookieError(localNode, cookie); } } else { if (!cookie.atomValue().equals("")) { - cookieError(self, cookie); + cookieError(localNode, cookie); } } cookieOk = true; @@ -610,12 +618,12 @@ public abstract class AbstractConnection extends Thread { } cookie = (OtpErlangAtom) head.elementAt(2); if (sendCookie) { - if (!cookie.atomValue().equals(self.cookie())) { - cookieError(self, cookie); + if (!cookie.atomValue().equals(localNode.cookie())) { + cookieError(localNode, cookie); } } else { if (!cookie.atomValue().equals("")) { - cookieError(self, cookie); + cookieError(localNode, cookie); } } cookieOk = true; @@ -749,13 +757,14 @@ public abstract class AbstractConnection extends Thread { final int oldLevel = traceLevel; // pin the value + int theLevel = level; if (level < 0) { - level = 0; + theLevel = 0; } else if (level > 4) { - level = 4; + theLevel = 4; } - traceLevel = level; + traceLevel = theLevel; return oldLevel; } @@ -908,18 +917,16 @@ public abstract class AbstractConnection extends Thread { int got = 0; final int len = b.length; int i; - InputStream is = null; synchronized (this) { if (s == null) { throw new IOException("expected " + len + " bytes, socket was closed"); } - is = s.getInputStream(); } while (got < len) { - i = is.read(b, got, len - got); + i = s.getInputStream().read(b, got, len - got); if (i < 0) { throw new IOException("expected " + len @@ -943,9 +950,9 @@ public abstract class AbstractConnection extends Thread { try { sendStatus("ok"); final int our_challenge = genChallenge(); - sendChallenge(peer.distChoose, self.flags, our_challenge); + sendChallenge(peer.distChoose, localNode.flags, our_challenge); final int her_challenge = recvChallengeReply(our_challenge); - final byte[] our_digest = genDigest(her_challenge, self.cookie()); + final byte[] our_digest = genDigest(her_challenge, localNode.cookie()); sendChallengeAck(our_digest); connected = true; cookieOk = true; @@ -978,10 +985,10 @@ public abstract class AbstractConnection extends Thread { System.out.println("-> MD5 CONNECT TO " + peer.host() + ":" + port); } - sendName(peer.distChoose, self.flags); + sendName(peer.distChoose, localNode.flags); recvStatus(); final int her_challenge = recvChallenge(); - final byte[] our_digest = genDigest(her_challenge, self.cookie()); + final byte[] our_digest = genDigest(her_challenge, localNode.cookie()); final int our_challenge = genChallenge(); sendChallengeReply(our_challenge, our_digest); recvChallengeAck(our_challenge); @@ -1054,33 +1061,35 @@ public abstract class AbstractConnection extends Thread { return res; } - protected void sendName(final int dist, final int flags) throws IOException { + protected void sendName(final int dist, final int aflags) throws IOException { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); - final String str = self.node(); + final String str = localNode.node(); obuf.write2BE(str.length() + 7); // 7 bytes + nodename obuf.write1(AbstractNode.NTYPE_R6); obuf.write2BE(dist); - obuf.write4BE(flags); + obuf.write4BE(aflags); obuf.write(str.getBytes()); obuf.writeTo(socket.getOutputStream()); if (traceLevel >= handshakeThreshold) { - System.out.println("-> " + "HANDSHAKE sendName" + " flags=" + flags - + " dist=" + dist + " local=" + self); + System.out.println("-> " + "HANDSHAKE sendName" + " flags=" + aflags + + " dist=" + dist + " local=" + localNode); } } - protected void sendChallenge(final int dist, final int flags, + protected void sendChallenge(final int dist, final int aflags, final int challenge) throws IOException { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); - final String str = self.node(); + final String str = localNode.node(); obuf.write2BE(str.length() + 11); // 11 bytes + nodename obuf.write1(AbstractNode.NTYPE_R6); obuf.write2BE(dist); - obuf.write4BE(flags); + obuf.write4BE(aflags); obuf.write4BE(challenge); obuf.write(str.getBytes()); @@ -1088,8 +1097,8 @@ public abstract class AbstractConnection extends Thread { if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendChallenge" + " flags=" - + flags + " dist=" + dist + " challenge=" + challenge - + " local=" + self); + + aflags + " dist=" + dist + " challenge=" + challenge + + " local=" + localNode); } } @@ -1100,6 +1109,7 @@ public abstract class AbstractConnection extends Thread { byte[] tmpbuf; readSock(socket, lbuf); + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(lbuf, 0); final int len = ibuf.read2BE(); tmpbuf = new byte[len]; @@ -1107,41 +1117,42 @@ public abstract class AbstractConnection extends Thread { return tmpbuf; } - protected void recvName(final OtpPeer peer) throws IOException { + protected void recvName(final OtpPeer apeer) throws IOException { String hisname = ""; try { final byte[] tmpbuf = read2BytePackage(); + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); byte[] tmpname; final int len = tmpbuf.length; - peer.ntype = ibuf.read1(); - if (peer.ntype != AbstractNode.NTYPE_R6) { + apeer.ntype = ibuf.read1(); + if (apeer.ntype != AbstractNode.NTYPE_R6) { throw new IOException("Unknown remote node type"); } - peer.distLow = peer.distHigh = ibuf.read2BE(); - if (peer.distLow < 5) { + apeer.distLow = apeer.distHigh = ibuf.read2BE(); + if (apeer.distLow < 5) { throw new IOException("Unknown remote node type"); } - peer.flags = ibuf.read4BE(); + apeer.flags = ibuf.read4BE(); tmpname = new byte[len - 7]; ibuf.readN(tmpname); hisname = OtpErlangString.newString(tmpname); // Set the old nodetype parameter to indicate hidden/normal status // When the old handshake is removed, the ntype should also be. - if ((peer.flags & AbstractNode.dFlagPublished) != 0) { - peer.ntype = AbstractNode.NTYPE_R4_ERLANG; + if ((apeer.flags & AbstractNode.dFlagPublished) != 0) { + apeer.ntype = AbstractNode.NTYPE_R4_ERLANG; } else { - peer.ntype = AbstractNode.NTYPE_R4_HIDDEN; + apeer.ntype = AbstractNode.NTYPE_R4_HIDDEN; } - if ((peer.flags & AbstractNode.dFlagExtendedReferences) == 0) { + if ((apeer.flags & AbstractNode.dFlagExtendedReferences) == 0) { throw new IOException( "Handshake failed - peer cannot handle extended references"); } - if ((peer.flags & AbstractNode.dFlagExtendedPidsPorts) == 0) { + if ((apeer.flags & AbstractNode.dFlagExtendedPidsPorts) == 0) { throw new IOException( "Handshake failed - peer cannot handle extended pids and ports"); } @@ -1151,13 +1162,13 @@ public abstract class AbstractConnection extends Thread { } final int i = hisname.indexOf('@', 0); - peer.node = hisname; - peer.alive = hisname.substring(0, i); - peer.host = hisname.substring(i + 1, hisname.length()); + apeer.node = hisname; + apeer.alive = hisname.substring(0, i); + apeer.host = hisname.substring(i + 1, hisname.length()); if (traceLevel >= handshakeThreshold) { - System.out.println("<- " + "HANDSHAKE" + " ntype=" + peer.ntype - + " dist=" + peer.distHigh + " remote=" + peer); + System.out.println("<- " + "HANDSHAKE" + " ntype=" + apeer.ntype + + " dist=" + apeer.distHigh + " remote=" + apeer); } } @@ -1167,6 +1178,7 @@ public abstract class AbstractConnection extends Thread { try { final byte[] buf = read2BytePackage(); + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(buf, 0); peer.ntype = ibuf.read1(); if (peer.ntype != AbstractNode.NTYPE_R6) { @@ -1199,7 +1211,7 @@ public abstract class AbstractConnection extends Thread { if (traceLevel >= handshakeThreshold) { System.out.println("<- " + "HANDSHAKE recvChallenge" + " from=" - + peer.node + " challenge=" + challenge + " local=" + self); + + peer.node + " challenge=" + challenge + " local=" + localNode); } return challenge; @@ -1208,6 +1220,7 @@ public abstract class AbstractConnection extends Thread { protected void sendChallengeReply(final int challenge, final byte[] digest) throws IOException { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); obuf.write2BE(21); obuf.write1(ChallengeReply); @@ -1218,7 +1231,7 @@ public abstract class AbstractConnection extends Thread { if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendChallengeReply" + " challenge=" + challenge + " digest=" + hex(digest) - + " local=" + self); + + " local=" + localNode); } } @@ -1241,6 +1254,7 @@ public abstract class AbstractConnection extends Thread { try { final byte[] buf = read2BytePackage(); + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(buf, 0); final int tag = ibuf.read1(); if (tag != ChallengeReply) { @@ -1248,7 +1262,7 @@ public abstract class AbstractConnection extends Thread { } challenge = ibuf.read4BE(); ibuf.readN(her_digest); - final byte[] our_digest = genDigest(our_challenge, self.cookie()); + final byte[] our_digest = genDigest(our_challenge, localNode.cookie()); if (!digests_equals(her_digest, our_digest)) { throw new OtpAuthException("Peer authentication error."); } @@ -1259,7 +1273,7 @@ public abstract class AbstractConnection extends Thread { if (traceLevel >= handshakeThreshold) { System.out.println("<- " + "HANDSHAKE recvChallengeReply" + " from=" + peer.node + " challenge=" + challenge - + " digest=" + hex(her_digest) + " local=" + self); + + " digest=" + hex(her_digest) + " local=" + localNode); } return challenge; @@ -1267,6 +1281,7 @@ public abstract class AbstractConnection extends Thread { protected void sendChallengeAck(final byte[] digest) throws IOException { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); obuf.write2BE(17); obuf.write1(ChallengeAck); @@ -1276,7 +1291,7 @@ public abstract class AbstractConnection extends Thread { if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendChallengeAck" - + " digest=" + hex(digest) + " local=" + self); + + " digest=" + hex(digest) + " local=" + localNode); } } @@ -1286,13 +1301,14 @@ public abstract class AbstractConnection extends Thread { final byte[] her_digest = new byte[16]; try { final byte[] buf = read2BytePackage(); + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(buf, 0); final int tag = ibuf.read1(); if (tag != ChallengeAck) { throw new IOException("Handshake protocol error"); } ibuf.readN(her_digest); - final byte[] our_digest = genDigest(our_challenge, self.cookie()); + final byte[] our_digest = genDigest(our_challenge, localNode.cookie()); if (!digests_equals(her_digest, our_digest)) { throw new OtpAuthException("Peer authentication error."); } @@ -1305,12 +1321,13 @@ public abstract class AbstractConnection extends Thread { if (traceLevel >= handshakeThreshold) { System.out.println("<- " + "HANDSHAKE recvChallengeAck" + " from=" + peer.node + " digest=" + hex(her_digest) + " local=" - + self); + + localNode); } } protected void sendStatus(final String status) throws IOException { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); obuf.write2BE(status.length() + 1); obuf.write1(ChallengeStatus); @@ -1320,7 +1337,7 @@ public abstract class AbstractConnection extends Thread { if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendStatus" + " status=" - + status + " local=" + self); + + status + " local=" + localNode); } } @@ -1328,6 +1345,7 @@ public abstract class AbstractConnection extends Thread { try { final byte[] buf = read2BytePackage(); + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(buf, 0); final int tag = ibuf.read1(); if (tag != ChallengeStatus) { @@ -1346,7 +1364,7 @@ public abstract class AbstractConnection extends Thread { } if (traceLevel >= handshakeThreshold) { System.out.println("<- " + "HANDSHAKE recvStatus (ok)" + " local=" - + self); + + localNode); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java index 3ef44b8851..3bb1bbbd18 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java @@ -128,7 +128,12 @@ public class AbstractNode { final File dotCookieFile = new File(dotCookieFilename); br = new BufferedReader(new FileReader(dotCookieFile)); - defaultCookie = br.readLine().trim(); + final String line = br.readLine(); + if (line == null) { + defaultCookie = ""; + } else { + defaultCookie = line.trim(); + } } catch (final IOException e) { defaultCookie = ""; } finally { @@ -260,8 +265,7 @@ public class AbstractNode { final String drive = System.getenv("HOMEDRIVE"); final String path = System.getenv("HOMEPATH"); return (drive != null && path != null) ? drive + path : home; - } else { - return home; } + return home; } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java index 2b085761e3..c8b4fcebde 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java @@ -41,11 +41,12 @@ class Link { return local.equals(pid) || remote.equals(pid); } - public boolean equals(final OtpErlangPid local, final OtpErlangPid remote) { - return this.local.equals(local) && this.remote.equals(remote) - || this.local.equals(remote) && this.remote.equals(local); + public boolean equals(final OtpErlangPid alocal, final OtpErlangPid aremote) { + return local.equals(alocal) && remote.equals(aremote) + || local.equals(aremote) && remote.equals(alocal); } + @Override public int hashCode() { if (hashCodeValue == 0) { OtpErlangObject.Hash hash = new OtpErlangObject.Hash(5); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile b/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile index f476d4594d..fd923f85ae 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile @@ -32,6 +32,15 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk include $(ERL_TOP)/lib/jinterface/vsn.mk VSN=$(JINTERFACE_VSN) +# + +EBINDIR=$(ERL_TOP)/lib/jinterface/ebin + +APP_FILE= jinterface.app + +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBINDIR)/$(APP_FILE) + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- @@ -45,7 +54,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/jinterface-$(VSN) # all java sourcefiles listed in common include file include $(ERL_TOP)/lib/jinterface/java_src/$(JAVA_CLASS_SUBDIR)/java_files -TARGET_FILES= $(JAVA_FILES:%=$(JAVA_DEST_ROOT)$(JAVA_CLASS_SUBDIR)%.class) +TARGET_FILES= $(JAVA_FILES:%=$(JAVA_DEST_ROOT)$(JAVA_CLASS_SUBDIR)%.class) $(APP_TARGET) JAVA_SRC= $(JAVA_FILES:%=%.java) JARFILE= OtpErlang.jar @@ -66,7 +75,7 @@ ifneq ($(V),0) JARFLAGS=-cfv endif -JAVA_OPTIONS = +JAVA_OPTIONS = -Xlint ifeq ($(TESTROOT),) RELEASE_PATH="$(ERL_TOP)/release/$(TARGET)" @@ -79,6 +88,9 @@ endif # Make Rules # ---------------------------------------------------- +$(APP_TARGET): $(APP_SRC) $(ERL_TOP)/lib/jinterface/vsn.mk + $(vsn_verbose)sed -e 's;%VSN%;$(JINTERFACE_VSN);' $< > $@ + debug opt: make_dirs $(JAVA_DEST_ROOT)$(JARFILE) make_dirs: @@ -106,6 +118,8 @@ release_spec: opt $(V_at)$(INSTALL_DATA) $(JAVA_SRC) "$(RELSYSDIR)/java_src/com/ericsson/otp/erlang" $(V_at)$(INSTALL_DIR) "$(RELSYSDIR)/priv" $(V_at)$(INSTALL_DATA) $(JAVA_DEST_ROOT)$(JARFILE) "$(RELSYSDIR)/priv" + $(V_at)$(INSTALL_DIR) "$(RELSYSDIR)/ebin" + $(V_at)$(INSTALL_DATA) $(APP_TARGET) "$(RELSYSDIR)/ebin/$(APP_FILE)" release_docs_spec: @@ -113,4 +127,3 @@ release_docs_spec: # ---------------------------------------------------- - diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java index e7a9d1092c..9ad02506fd 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java @@ -358,6 +358,7 @@ public class OtpConnection extends AbstractConnection { * if the connection is not active or a communication * error occurs. */ + @SuppressWarnings("resource") public void send(final OtpErlangPid dest, final OtpErlangObject msg) throws IOException { // encode and send the message @@ -376,6 +377,7 @@ public class OtpConnection extends AbstractConnection { * if the connection is not active or a communication * error occurs. */ + @SuppressWarnings("resource") public void send(final String dest, final OtpErlangObject msg) throws IOException { // encode and send the message diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java index 5abf6e33f7..43b0cad222 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java @@ -149,6 +149,7 @@ public class OtpCookedConnection extends AbstractConnection { /* * send to pid */ + @SuppressWarnings("resource") void send(final OtpErlangPid from, final OtpErlangPid dest, final OtpErlangObject msg) throws IOException { // encode and send the message @@ -159,6 +160,7 @@ public class OtpCookedConnection extends AbstractConnection { * send to remote name dest is recipient's registered name, the nodename is * implied by the choice of connection. */ + @SuppressWarnings("resource") void send(final OtpErlangPid from, final String dest, final OtpErlangObject msg) throws IOException { // encode and send the message diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java index 1868dc7740..8a8ba785d9 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java @@ -160,6 +160,7 @@ public class OtpEpmd { try { s = new Socket((String) null, EpmdPort.get()); + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); obuf.write2BE(node.alive().length() + 1); obuf.write1(stopReq); @@ -189,6 +190,7 @@ public class OtpEpmd { Socket s = null; try { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); s = new Socket(node.host(), EpmdPort.get()); @@ -219,6 +221,7 @@ public class OtpEpmd { + node.host() + " when looking up " + node.alive()); } + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); final int response = ibuf.read1(); @@ -279,6 +282,7 @@ public class OtpEpmd { Socket s = null; try { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); s = new Socket((String) null, EpmdPort.get()); @@ -310,13 +314,12 @@ public class OtpEpmd { final int n = s.getInputStream().read(tmpbuf); if (n < 0) { - if (s != null) { s.close(); - } throw new IOException("Nameserver not responding on " + node.host() + " when publishing " + node.alive()); } + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); final int response = ibuf.read1(); @@ -341,9 +344,7 @@ public class OtpEpmd { throw new IOException("Nameserver not responding on " + node.host() + " when publishing " + node.alive()); } catch (final OtpErlangDecodeException e) { - if (s != null) { s.close(); - } if (traceLevel >= traceThreshold) { System.out.println("<- (invalid response)"); } @@ -351,9 +352,7 @@ public class OtpEpmd { + " when publishing " + node.alive()); } - if (s != null) { s.close(); - } return null; } @@ -366,6 +365,7 @@ public class OtpEpmd { Socket s = null; try { + @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); try { s = new Socket(address, EpmdPort.get()); @@ -390,6 +390,7 @@ public class OtpEpmd { out.write(buffer, 0, bytesRead); } final byte[] tmpbuf = out.toByteArray(); + @SuppressWarnings("resource") final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); ibuf.read4BE(); // read port int // final int port = ibuf.read4BE(); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java index 0371740b26..bff3e2c0e3 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java @@ -18,17 +18,15 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang atoms. Atoms can be created from * strings whose length is not more than {@link #maxAtomLength maxAtomLength} * characters. */ -public class OtpErlangAtom extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangAtom extends OtpErlangObject { // don't change this! - static final long serialVersionUID = -3204386396807876641L; + private static final long serialVersionUID = -3204386396807876641L; /** The maximun allowed length of an atom, in characters */ public static final int maxAtomLength = 0xff; // one byte length @@ -119,9 +117,8 @@ public class OtpErlangAtom extends OtpErlangObject implements Serializable, public String toString() { if (atomNeedsQuoting(atom)) { return "'" + escapeSpecialChars(atom) + "'"; - } else { - return atom; } + return atom; } /** @@ -139,8 +136,8 @@ public class OtpErlangAtom extends OtpErlangObject implements Serializable, return false; } - final OtpErlangAtom atom = (OtpErlangAtom) o; - return this.atom.compareTo(atom.atom) == 0; + final OtpErlangAtom other = (OtpErlangAtom) o; + return this.atom.compareTo(other.atom) == 0; } @Override diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBinary.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBinary.java index a9eaad540e..0891781f8d 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBinary.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBinary.java @@ -18,16 +18,14 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang binaries. Anything that can be * represented as a sequence of bytes can be made into an Erlang binary. */ -public class OtpErlangBinary extends OtpErlangBitstr implements Serializable, - Cloneable { +public class OtpErlangBinary extends OtpErlangBitstr { // don't change this! - static final long serialVersionUID = -3781009633593609217L; + private static final long serialVersionUID = -3781009633593609217L; /** * Create a binary from a byte array diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBitstr.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBitstr.java index 97897fe182..8cb4e0e685 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBitstr.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBitstr.java @@ -20,17 +20,15 @@ package com.ericsson.otp.erlang; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.Serializable; /** * Provides a Java representation of Erlang bitstrs. An Erlang bitstr is an * Erlang binary with a length not an integral number of bytes (8-bit). Anything * can be represented as a sequence of bytes can be made into an Erlang bitstr. */ -public class OtpErlangBitstr extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangBitstr extends OtpErlangObject { // don't change this! - static final long serialVersionUID = -3781009633593609217L; + private static final long serialVersionUID = -3781009633593609217L; protected byte[] bin; protected int pad_bits; @@ -63,18 +61,18 @@ public class OtpErlangBitstr extends OtpErlangObject implements Serializable, check_bitstr(this.bin, this.pad_bits); } - private void check_bitstr(final byte[] bin, final int pad_bits) { - if (pad_bits < 0 || 7 < pad_bits) { + private void check_bitstr(final byte[] abin, final int a_pad_bits) { + if (a_pad_bits < 0 || 7 < a_pad_bits) { throw new java.lang.IllegalArgumentException( "Padding must be in range 0..7"); } - if (pad_bits != 0 && bin.length == 0) { + if (a_pad_bits != 0 && abin.length == 0) { throw new java.lang.IllegalArgumentException( "Padding on zero length bitstr"); } - if (bin.length != 0) { + if (abin.length != 0) { // Make sure padding is zero - bin[bin.length - 1] &= ~((1 << pad_bits) - 1); + abin[abin.length - 1] &= ~((1 << a_pad_bits) - 1); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBoolean.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBoolean.java index b97b5b7d90..eecd2ea288 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBoolean.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBoolean.java @@ -18,14 +18,12 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang booleans, which are special cases of * atoms with values 'true' and 'false'. */ -public class OtpErlangBoolean extends OtpErlangAtom implements Serializable, - Cloneable { +public class OtpErlangBoolean extends OtpErlangAtom { // don't change this! static final long serialVersionUID = 1087178844844988393L; diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangByte.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangByte.java index 2d598c119e..eb6f3d8aba 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangByte.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangByte.java @@ -18,15 +18,13 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang integral types. */ -public class OtpErlangByte extends OtpErlangLong implements Serializable, - Cloneable { +public class OtpErlangByte extends OtpErlangLong { // don't change this! - static final long serialVersionUID = 5778019796466613446L; + private static final long serialVersionUID = 5778019796466613446L; /** * Create an Erlang integer from the given value. @@ -56,6 +54,6 @@ public class OtpErlangByte extends OtpErlangLong implements Serializable, throws OtpErlangRangeException, OtpErlangDecodeException { super(buf); - final byte i = byteValue(); + byteValue(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangChar.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangChar.java index b442bbcec1..e7c6dd8ad4 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangChar.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangChar.java @@ -18,15 +18,13 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang integral types. */ -public class OtpErlangChar extends OtpErlangLong implements Serializable, - Cloneable { +public class OtpErlangChar extends OtpErlangLong { // don't change this! - static final long serialVersionUID = 3225337815669398204L; + private static final long serialVersionUID = 3225337815669398204L; /** * Create an Erlang integer from the given value. @@ -56,6 +54,6 @@ public class OtpErlangChar extends OtpErlangLong implements Serializable, throws OtpErlangRangeException, OtpErlangDecodeException { super(buf); - final char i = charValue(); + charValue(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDecodeException.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDecodeException.java index db55deaedf..6986e26908 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDecodeException.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDecodeException.java @@ -26,6 +26,8 @@ package com.ericsson.otp.erlang; * @see OtpInputStream */ public class OtpErlangDecodeException extends OtpErlangException { + private static final long serialVersionUID = 1L; + /** * Provides a detailed message. */ diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDouble.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDouble.java index 793940e858..e92ce11431 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDouble.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDouble.java @@ -18,7 +18,6 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang floats and doubles. Erlang defines @@ -26,10 +25,9 @@ import java.io.Serializable; * {@link OtpErlangFloat} are used to provide representations corresponding to * the Java types Double and Float. */ -public class OtpErlangDouble extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangDouble extends OtpErlangObject { // don't change this! - static final long serialVersionUID = 132947104811974021L; + private static final long serialVersionUID = 132947104811974021L; private final double d; @@ -120,8 +118,8 @@ public class OtpErlangDouble extends OtpErlangObject implements Serializable, return false; } - final OtpErlangDouble d = (OtpErlangDouble) o; - return this.d == d.d; + final OtpErlangDouble other = (OtpErlangDouble) o; + return this.d == other.d; } @Override diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFloat.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFloat.java index 8662b74c53..7d48f848f0 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFloat.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFloat.java @@ -18,15 +18,13 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang floats and doubles. */ -public class OtpErlangFloat extends OtpErlangDouble implements Serializable, - Cloneable { +public class OtpErlangFloat extends OtpErlangDouble { // don't change this! - static final long serialVersionUID = -2231546377289456934L; + private static final long serialVersionUID = -2231546377289456934L; /** * Create an Erlang float from the given float value. @@ -53,6 +51,6 @@ public class OtpErlangFloat extends OtpErlangDouble implements Serializable, throws OtpErlangDecodeException, OtpErlangRangeException { super(buf); - final float f = floatValue(); + floatValue(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFun.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFun.java index c52909acc5..05fa0cbb23 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFun.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFun.java @@ -18,10 +18,9 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; import java.util.Arrays; -public class OtpErlangFun extends OtpErlangObject implements Serializable { +public class OtpErlangFun extends OtpErlangObject { // don't change this! private static final long serialVersionUID = -3423031125356706472L; diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangInt.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangInt.java index d947421459..741fc29dd0 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangInt.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangInt.java @@ -18,15 +18,13 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang integral types. */ -public class OtpErlangInt extends OtpErlangLong implements Serializable, - Cloneable { +public class OtpErlangInt extends OtpErlangLong { // don't change this! - static final long serialVersionUID = 1229430977614805556L; + private static final long serialVersionUID = 1229430977614805556L; /** * Create an Erlang integer from the given value. @@ -56,6 +54,6 @@ public class OtpErlangInt extends OtpErlangLong implements Serializable, throws OtpErlangRangeException, OtpErlangDecodeException { super(buf); - final int j = intValue(); + intValue(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangList.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangList.java index 3456fd7412..9f7c5f5499 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangList.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangList.java @@ -18,7 +18,6 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; @@ -30,9 +29,9 @@ import java.util.NoSuchElementException; * The arity of the list is the number of elements it contains. */ public class OtpErlangList extends OtpErlangObject implements - Iterable<OtpErlangObject>, Serializable, Cloneable { + Iterable<OtpErlangObject> { // don't change this! - static final long serialVersionUID = 5999112769036676548L; + private static final long serialVersionUID = 5999112769036676548L; private static final OtpErlangObject[] NO_ELEMENTS = new OtpErlangObject[0]; @@ -187,12 +186,11 @@ public class OtpErlangList extends OtpErlangObject implements public OtpErlangObject[] elements() { if (arity() == 0) { return NO_ELEMENTS; - } else { + } final OtpErlangObject[] res = new OtpErlangObject[arity()]; System.arraycopy(elems, 0, res, 0, res.length); return res; } - } /** * Get the string representation of the list. @@ -326,7 +324,7 @@ public class OtpErlangList extends OtpErlangObject implements try { return new OtpErlangList(elements(), getLastTail()); } catch (final OtpErlangException e) { - return null; + throw new AssertionError(this); } } @@ -361,9 +359,8 @@ public class OtpErlangList extends OtpErlangObject implements if (arity >= n) { if (arity == n && lastTail != null) { return lastTail; - } else { - return new SubList(this, n); } + return new SubList(this, n); } return null; } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangLong.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangLong.java index 84b1355c54..c6021a6ae1 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangLong.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangLong.java @@ -18,7 +18,6 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; import java.math.BigInteger; /** @@ -30,10 +29,9 @@ import java.math.BigInteger; * {@link OtpErlangUInt} and {@link OtpErlangUShort} are provided for Corba * compatibility. See the documentation for IC for more information. */ -public class OtpErlangLong extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangLong extends OtpErlangObject { // don't change this! - static final long serialVersionUID = 1610466859236755096L; + private static final long serialVersionUID = 1610466859236755096L; private long val; private BigInteger bigVal = null; @@ -94,9 +92,8 @@ public class OtpErlangLong extends OtpErlangObject implements Serializable, public BigInteger bigIntegerValue() { if (bigVal != null) { return bigVal; - } else { - return BigInteger.valueOf(val); } + return BigInteger.valueOf(val); } /** @@ -109,9 +106,8 @@ public class OtpErlangLong extends OtpErlangObject implements Serializable, public long longValue() { if (bigVal != null) { return bigVal.longValue(); - } else { - return val; } + return val; } /** @@ -159,7 +155,7 @@ public class OtpErlangLong extends OtpErlangObject implements Serializable, } if (val == 0 || val == -1) { return 0; - } else { + } // Binary search for bit length int i = 32; // mask length long m = (1L << i) - 1; // AND mask with ones in little end @@ -193,7 +189,6 @@ public class OtpErlangLong extends OtpErlangObject implements Serializable, } return i; } - } /** * Return the signum function of this object. @@ -203,9 +198,8 @@ public class OtpErlangLong extends OtpErlangObject implements Serializable, public int signum() { if (bigVal != null) { return bigVal.signum(); - } else { - return val > 0 ? 1 : val < 0 ? -1 : 0; } + return val > 0 ? 1 : val < 0 ? -1 : 0; } /** @@ -342,9 +336,8 @@ public class OtpErlangLong extends OtpErlangObject implements Serializable, public String toString() { if (bigVal != null) { return "" + bigVal; - } else { - return "" + val; } + return "" + val; } /** @@ -392,8 +385,7 @@ public class OtpErlangLong extends OtpErlangObject implements Serializable, protected int doHashCode() { if (bigVal != null) { return bigVal.hashCode(); - } else { - return BigInteger.valueOf(val).hashCode(); } + return BigInteger.valueOf(val).hashCode(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangMap.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangMap.java index 0254edd5da..7f1a64b87d 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangMap.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangMap.java @@ -18,7 +18,6 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang maps. Maps are created from one or @@ -29,8 +28,7 @@ import java.io.Serializable; * values can be retrieved as arrays and the value for a key can be queried. * */ -public class OtpErlangMap extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangMap extends OtpErlangObject { // don't change this! private static final long serialVersionUID = -6410770117696198497L; diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangObject.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangObject.java index 81220c5685..5215e5887b 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangObject.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangObject.java @@ -171,14 +171,14 @@ public abstract class OtpErlangObject implements Serializable, Cloneable { for (j = 0, k = 0; j + 4 < b.length; j += 4, k += 1, k %= 3) { - abc[k] += ((int)b[j+0] & 0xFF) + ((int)b[j+1]<<8 & 0xFF00) - + ((int)b[j+2]<<16 & 0xFF0000) + ((int)b[j+3]<<24); + abc[k] += (b[j+0] & 0xFF) + (b[j+1]<<8 & 0xFF00) + + (b[j+2]<<16 & 0xFF0000) + (b[j+3]<<24); mix(); } for (int n = 0, m = 0xFF; j < b.length; j++, n += 8, m <<= 8) { - abc[k] += (int)b[j]<<n & m; + abc[k] += b[j]<<n & m; } mix(); } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java index f75e4353d0..4c9f5c78a3 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java @@ -18,16 +18,14 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang PIDs. PIDs represent Erlang * processes and consist of a nodename and a number of integers. */ -public class OtpErlangPid extends OtpErlangObject implements Serializable, - Cloneable, Comparable<Object> { +public class OtpErlangPid extends OtpErlangObject implements Comparable<Object> { // don't change this! - static final long serialVersionUID = 1664394142301803659L; + private static final long serialVersionUID = 1664394142301803659L; private final String node; private final int id; @@ -197,14 +195,11 @@ public class OtpErlangPid extends OtpErlangObject implements Serializable, if (serial == pid.serial) { if (id == pid.id) { return node.compareTo(pid.node); - } else { + } return id - pid.id; } - } else { return serial - pid.serial; } - } else { return creation - pid.creation; } } -} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java index 2a0eab0a9c..8557e17325 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java @@ -18,15 +18,13 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang ports. */ -public class OtpErlangPort extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangPort extends OtpErlangObject { // don't change this! - static final long serialVersionUID = 4037115468007644704L; + private static final long serialVersionUID = 4037115468007644704L; private final String node; private final int id; @@ -40,6 +38,7 @@ public class OtpErlangPort extends OtpErlangObject implements Serializable, * * @deprecated use OtpLocalNode:createPort() instead */ + @SuppressWarnings("unused") private OtpErlangPort(final OtpSelf self) { final OtpErlangPort p = self.createPort(); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java index 8056439962..13a83333fa 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java @@ -18,17 +18,15 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang refs. There are two styles of Erlang * refs, old style (one id value) and new style (array of id values). This class * manages both types. */ -public class OtpErlangRef extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangRef extends OtpErlangObject { // don't change this! - static final long serialVersionUID = -7022666480768586521L; + private static final long serialVersionUID = -7022666480768586521L; private final String node; private final int creation; diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangShort.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangShort.java index cd232570dd..6ef56defbd 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangShort.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangShort.java @@ -18,13 +18,11 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang integral types. */ -public class OtpErlangShort extends OtpErlangLong implements Serializable, - Cloneable { +public class OtpErlangShort extends OtpErlangLong { // don't change this! static final long serialVersionUID = 7162345156603088099L; @@ -57,7 +55,7 @@ public class OtpErlangShort extends OtpErlangLong implements Serializable, throws OtpErlangRangeException, OtpErlangDecodeException { super(buf); - final short j = shortValue(); + shortValue(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangString.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangString.java index a5e202c473..1bccfcc567 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangString.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangString.java @@ -18,17 +18,14 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; -import java.lang.Character; import java.io.UnsupportedEncodingException; /** * Provides a Java representation of Erlang strings. */ -public class OtpErlangString extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangString extends OtpErlangObject { // don't change this! - static final long serialVersionUID = -7053595217604929233L; + private static final long serialVersionUID = -7053595217604929233L; private final String str; @@ -138,6 +135,7 @@ public class OtpErlangString extends OtpErlangObject implements Serializable, return false; } + @Override protected int doHashCode() { return str.hashCode(); } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangTuple.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangTuple.java index bffce7f14d..dffaa530cd 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangTuple.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangTuple.java @@ -18,7 +18,6 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang tuples. Tuples are created from one @@ -29,10 +28,9 @@ import java.io.Serializable; * indexed from 0 to (arity-1) and can be retrieved individually by using the * appropriate index. */ -public class OtpErlangTuple extends OtpErlangObject implements Serializable, - Cloneable { +public class OtpErlangTuple extends OtpErlangObject { // don't change this! - static final long serialVersionUID = 9163498658004915935L; + private static final long serialVersionUID = 9163498658004915935L; private static final OtpErlangObject[] NO_ELEMENTS = new OtpErlangObject[0]; @@ -51,9 +49,8 @@ public class OtpErlangTuple extends OtpErlangObject implements Serializable, if (elem == null) { throw new java.lang.IllegalArgumentException( "Tuple element cannot be null"); - } else { - elems = new OtpErlangObject[] { elem }; } + elems = new OtpErlangObject[] { elem }; } /** @@ -242,6 +239,7 @@ public class OtpErlangTuple extends OtpErlangObject implements Serializable, return true; } + @Override protected int doHashCode() { OtpErlangObject.Hash hash = new OtpErlangObject.Hash(9); final int a = arity(); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUInt.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUInt.java index f01354d821..a02996e437 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUInt.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUInt.java @@ -18,13 +18,11 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang integral types. */ -public class OtpErlangUInt extends OtpErlangLong implements Serializable, - Cloneable { +public class OtpErlangUInt extends OtpErlangLong { // don't change this! static final long serialVersionUID = -1450956122937471885L; @@ -40,7 +38,7 @@ public class OtpErlangUInt extends OtpErlangLong implements Serializable, public OtpErlangUInt(final int i) throws OtpErlangRangeException { super(i); - final int j = uIntValue(); + uIntValue(); } /** @@ -62,6 +60,6 @@ public class OtpErlangUInt extends OtpErlangLong implements Serializable, throws OtpErlangRangeException, OtpErlangDecodeException { super(buf); - final int j = uIntValue(); + uIntValue(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUShort.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUShort.java index 6b6bc7a56b..e9d251f815 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUShort.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUShort.java @@ -18,13 +18,11 @@ */ package com.ericsson.otp.erlang; -import java.io.Serializable; /** * Provides a Java representation of Erlang integral types. */ -public class OtpErlangUShort extends OtpErlangLong implements Serializable, - Cloneable { +public class OtpErlangUShort extends OtpErlangLong { // don't change this! static final long serialVersionUID = 300370950578307246L; @@ -40,7 +38,7 @@ public class OtpErlangUShort extends OtpErlangLong implements Serializable, public OtpErlangUShort(final short s) throws OtpErlangRangeException { super(s); - final short j = uShortValue(); + uShortValue(); } /** @@ -62,6 +60,6 @@ public class OtpErlangUShort extends OtpErlangLong implements Serializable, throws OtpErlangRangeException, OtpErlangDecodeException { super(buf); - final short j = uShortValue(); + uShortValue(); } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpException.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpException.java index 33d25b6021..874c7da104 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpException.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpException.java @@ -22,6 +22,8 @@ package com.ericsson.otp.erlang; * Base class for the other OTP exception classes. */ public abstract class OtpException extends Exception { + private static final long serialVersionUID = 1L; + /** * Provides no message. */ diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java index f813594541..bab0629382 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java @@ -85,16 +85,17 @@ public class OtpInputStream extends ByteArrayInputStream { * * @return the previous position in the stream. */ - public int setPos(int pos) { + public int setPos(final int pos) { final int oldpos = super.pos; + int apos = pos; if (pos > super.count) { - pos = super.count; + apos = super.count; } else if (pos < 0) { - pos = 0; + apos = 0; } - super.pos = pos; + super.pos = apos; return oldpos; } @@ -108,8 +109,8 @@ public class OtpInputStream extends ByteArrayInputStream { * @exception OtpErlangDecodeException * if the next byte cannot be read. */ - public int readN(final byte[] buf) throws OtpErlangDecodeException { - return this.readN(buf, 0, buf.length); + public int readN(final byte[] abuf) throws OtpErlangDecodeException { + return this.readN(abuf, 0, abuf.length); } /** @@ -121,12 +122,12 @@ public class OtpInputStream extends ByteArrayInputStream { * @exception OtpErlangDecodeException * if the next byte cannot be read. */ - public int readN(final byte[] buf, final int off, final int len) + public int readN(final byte[] abuf, final int off, final int len) throws OtpErlangDecodeException { if (len == 0 && available() == 0) { return 0; } - final int i = super.read(buf, off, len); + final int i = super.read(abuf, off, len); if (i < 0) { throw new OtpErlangDecodeException("Cannot read from input stream"); } @@ -214,7 +215,6 @@ public class OtpInputStream extends ByteArrayInputStream { } catch (final IOException e) { throw new OtpErlangDecodeException("Cannot read from input stream"); } - ; return (b[0] << 8 & 0xff00) + (b[1] & 0xff); } @@ -233,7 +233,6 @@ public class OtpInputStream extends ByteArrayInputStream { } catch (final IOException e) { throw new OtpErlangDecodeException("Cannot read from input stream"); } - ; return (b[0] << 24 & 0xff000000) + (b[1] << 16 & 0xff0000) + (b[2] << 8 & 0xff00) + (b[3] & 0xff); } @@ -253,7 +252,6 @@ public class OtpInputStream extends ByteArrayInputStream { } catch (final IOException e) { throw new OtpErlangDecodeException("Cannot read from input stream"); } - ; return (b[1] << 8 & 0xff00) + (b[0] & 0xff); } @@ -272,7 +270,6 @@ public class OtpInputStream extends ByteArrayInputStream { } catch (final IOException e) { throw new OtpErlangDecodeException("Cannot read from input stream"); } - ; return (b[3] << 24 & 0xff000000) + (b[2] << 16 & 0xff0000) + (b[1] << 8 & 0xff00) + (b[0] & 0xff); } @@ -288,17 +285,17 @@ public class OtpInputStream extends ByteArrayInputStream { * @exception OtpErlangDecodeException * if the next byte cannot be read. */ - public long readLE(int n) throws OtpErlangDecodeException { + public long readLE(final int n) throws OtpErlangDecodeException { final byte[] b = new byte[n]; try { super.read(b); } catch (final IOException e) { throw new OtpErlangDecodeException("Cannot read from input stream"); } - ; long v = 0; - while (n-- > 0) { - v = v << 8 | (long) b[n] & 0xff; + int i = n; + while (i-- > 0) { + v = v << 8 | (long) b[i] & 0xff; } return v; } @@ -321,7 +318,6 @@ public class OtpInputStream extends ByteArrayInputStream { } catch (final IOException e) { throw new OtpErlangDecodeException("Cannot read from input stream"); } - ; long v = 0; for (int i = 0; i < n; i++) { v = v << 8 | (long) b[i] & 0xff; @@ -350,6 +346,7 @@ public class OtpInputStream extends ByteArrayInputStream { * @exception OtpErlangDecodeException * if the next term in the stream is not an atom. */ + @SuppressWarnings("fallthrough") public String read_atom() throws OtpErlangDecodeException { int tag; int len = -1; @@ -382,7 +379,7 @@ public class OtpInputStream extends ByteArrayInputStream { case OtpExternal.smallAtomUtf8Tag: len = read1(); - /* fall through */ + // fall-through case OtpExternal.atomUtf8Tag: if (len < 0) { len = read2BE(); @@ -1055,7 +1052,7 @@ public class OtpInputStream extends ByteArrayInputStream { } return new OtpErlangFun(pid, module, index, uniq, freeVars); } else if (tag == OtpExternal.newFunTag) { - final int n = read4BE(); + read4BE(); final int arity = read1(); final byte[] md5 = new byte[16]; readN(md5); @@ -1149,13 +1146,13 @@ public class OtpInputStream extends ByteArrayInputStream { } final int size = read4BE(); - final byte[] buf = new byte[size]; + final byte[] abuf = new byte[size]; final java.util.zip.InflaterInputStream is = new java.util.zip.InflaterInputStream(this, new java.util.zip.Inflater(), size); int curPos = 0; try { int curRead; - while(curPos < size && (curRead = is.read(buf, curPos, size - curPos)) != -1) { + while(curPos < size && (curRead = is.read(abuf, curPos, size - curPos)) != -1) { curPos += curRead; } if (curPos != size) { @@ -1166,7 +1163,8 @@ public class OtpInputStream extends ByteArrayInputStream { throw new OtpErlangDecodeException("Cannot read from input stream"); } - final OtpInputStream ois = new OtpInputStream(buf, flags); + @SuppressWarnings("resource") + final OtpInputStream ois = new OtpInputStream(abuf, flags); return ois.read_any(); } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMD5.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMD5.java index 903a446258..a5a4d86602 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMD5.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMD5.java @@ -101,8 +101,11 @@ class OtpMD5 { private void to_buffer(int to_start, final int[] from, int from_start, int num) { - while (num-- > 0) { - buffer[to_start++] = from[from_start++]; + int ix = num; + int to_ix = to_start; + int from_ix = from_start; + while (ix-- > 0) { + buffer[to_ix++] = from[from_ix++]; } } @@ -121,7 +124,7 @@ class OtpMD5 { count[1] = plus(count[1], shr(inlen, 29)); - /* dumpstate(); */ + // dumpstate(); if (inlen >= partlen) { to_buffer(index, bytes, 0, (int) partlen); @@ -144,6 +147,7 @@ class OtpMD5 { } + @SuppressWarnings("unused") private void dumpstate() { System.out.println("state = {" + state[0] + ", " + state[1] + ", " + state[2] + ", " + state[3] + "}"); @@ -185,30 +189,30 @@ class OtpMD5 { private long FF(long a, final long b, final long c, final long d, final long x, final long s, final long ac) { - a = plus(a, plus(plus(F(b, c, d), x), ac)); - a = ROTATE_LEFT(a, s); - return plus(a, b); + long tmp = plus(a, plus(plus(F(b, c, d), x), ac)); + tmp = ROTATE_LEFT(tmp, s); + return plus(tmp, b); } private long GG(long a, final long b, final long c, final long d, final long x, final long s, final long ac) { - a = plus(a, plus(plus(G(b, c, d), x), ac)); - a = ROTATE_LEFT(a, s); - return plus(a, b); + long tmp = plus(a, plus(plus(G(b, c, d), x), ac)); + tmp = ROTATE_LEFT(tmp, s); + return plus(tmp, b); } private long HH(long a, final long b, final long c, final long d, final long x, final long s, final long ac) { - a = plus(a, plus(plus(H(b, c, d), x), ac)); - a = ROTATE_LEFT(a, s); - return plus(a, b); + long tmp = plus(a, plus(plus(H(b, c, d), x), ac)); + tmp = ROTATE_LEFT(tmp, s); + return plus(tmp, b); } private long II(long a, final long b, final long c, final long d, final long x, final long s, final long ac) { - a = plus(a, plus(plus(I(b, c, d), x), ac)); - a = ROTATE_LEFT(a, s); - return plus(a, b); + long tmp = plus(a, plus(plus(I(b, c, d), x), ac)); + tmp = ROTATE_LEFT(tmp, s); + return plus(tmp, b); } private void decode(final long output[], final int input[], diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java index 4a4a1e7f8f..fc592c222c 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java @@ -128,14 +128,14 @@ public class OtpMbox { * supercede that name. * </p> * - * @param name + * @param aname * the name to register for the mailbox. Specify null to * unregister the existing name from this mailbox. * * @return true if the name was available, or false otherwise. */ - public synchronized boolean registerName(final String name) { - return home.registerName(name, this); + public synchronized boolean registerName(final String aname) { + return home.registerName(aname, this); } /** @@ -350,21 +350,21 @@ public class OtpMbox { * Send a message to a named mailbox created from the same node as this * mailbox. * - * @param name + * @param aname * the registered name of recipient mailbox. * * @param msg * the body of the message to send. * */ - public void send(final String name, final OtpErlangObject msg) { - home.deliver(new OtpMsg(self, name, (OtpErlangObject) msg.clone())); + public void send(final String aname, final OtpErlangObject msg) { + home.deliver(new OtpMsg(self, aname, (OtpErlangObject) msg.clone())); } /** * Send a message to a named mailbox created from another node. * - * @param name + * @param aname * the registered name of recipient mailbox. * * @param node @@ -375,23 +375,23 @@ public class OtpMbox { * the body of the message to send. * */ - public void send(final String name, final String node, + public void send(final String aname, final String node, final OtpErlangObject msg) { try { final String currentNode = home.node(); if (node.equals(currentNode)) { - send(name, msg); + send(aname, msg); } else if (node.indexOf('@', 0) < 0 && node.equals(currentNode.substring(0, currentNode .indexOf('@', 0)))) { - send(name, msg); + send(aname, msg); } else { // other node final OtpCookedConnection conn = home.getConnection(node); if (conn == null) { return; } - conn.send(self, name, msg); + conn.send(self, aname, msg); } } catch (final Exception e) { } @@ -629,8 +629,8 @@ public class OtpMbox { * @return the {@link OtpErlangPid pid} corresponding to the registered * name, or null if the name is not known on this node. */ - public OtpErlangPid whereis(final String name) { - return home.whereis(name); + public OtpErlangPid whereis(final String aname) { + return home.whereis(aname); } /** diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java index 31a5d0fb8f..7c5bc69361 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java @@ -129,13 +129,14 @@ public class OtpMsg { } // other message types (link, unlink) - OtpMsg(int tag, final OtpErlangPid from, final OtpErlangPid to) { + OtpMsg(final int tag, final OtpErlangPid from, final OtpErlangPid to) { // convert TT-tags to equiv non-TT versions + int atag = tag; if (tag > 10) { - tag -= 10; + atag -= 10; } - this.tag = tag; + this.tag = atag; this.from = from; this.to = to; } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java index 7ead0b9c54..68addb9f2c 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java @@ -74,7 +74,7 @@ public class OtpNode extends OtpLocalNode { OtpNodeStatus handler; // flags - private int flags = 0; + private int connFlags = 0; /** * <p> @@ -143,12 +143,12 @@ public class OtpNode extends OtpLocalNode { init(port); } - private synchronized void init(final int port) throws IOException { + private synchronized void init(final int aport) throws IOException { if (!initDone) { connections = new Hashtable<String, OtpCookedConnection>(17, (float) 0.95); mboxes = new Mailboxes(); - acceptor = new Acceptor(port); + acceptor = new Acceptor(aport); initDone = true; } } @@ -314,13 +314,13 @@ public class OtpNode extends OtpLocalNode { * OtpNodeStatus} handler object contains callback methods, that will be * called when certain events occur. * - * @param handler + * @param ahandler * the callback object to register. To clear the handler, specify * null as the handler to use. * */ - public synchronized void registerStatusHandler(final OtpNodeStatus handler) { - this.handler = handler; + public synchronized void registerStatusHandler(final OtpNodeStatus ahandler) { + this.handler = ahandler; } /** @@ -344,7 +344,7 @@ public class OtpNode extends OtpLocalNode { * ; * </pre> * - * @param node + * @param anode * the name of the node to ping. * * @param timeout @@ -362,11 +362,11 @@ public class OtpNode extends OtpLocalNode { * * the reply: <- SEND {2,'',#Pid<[email protected]>} {#Ref<[email protected]>,yes} */ - public boolean ping(final String node, final long timeout) { - if (node.equals(this.node)) { + public boolean ping(final String anode, final long timeout) { + if (anode.equals(this.node)) { return true; - } else if (node.indexOf('@', 0) < 0 - && node.equals(this.node + } else if (anode.indexOf('@', 0) < 0 + && anode.equals(this.node .substring(0, this.node.indexOf('@', 0)))) { return true; } @@ -375,7 +375,7 @@ public class OtpNode extends OtpLocalNode { OtpMbox mbox = null; try { mbox = createMbox(); - mbox.send("net_kernel", node, getPingTuple(mbox)); + mbox.send("net_kernel", anode, getPingTuple(mbox)); final OtpErlangObject reply = mbox.receive(timeout); final OtpErlangTuple t = (OtpErlangTuple) reply; @@ -392,17 +392,17 @@ public class OtpNode extends OtpLocalNode { private OtpErlangTuple getPingTuple(final OtpMbox mbox) { final OtpErlangObject[] ping = new OtpErlangObject[3]; final OtpErlangObject[] pid = new OtpErlangObject[2]; - final OtpErlangObject[] node = new OtpErlangObject[2]; + final OtpErlangObject[] anode = new OtpErlangObject[2]; pid[0] = mbox.self(); pid[1] = createRef(); - node[0] = new OtpErlangAtom("is_auth"); - node[1] = new OtpErlangAtom(node()); + anode[0] = new OtpErlangAtom("is_auth"); + anode[1] = new OtpErlangAtom(node()); ping[0] = new OtpErlangAtom("$gen_call"); ping[1] = new OtpErlangTuple(pid); - ping[2] = new OtpErlangTuple(node); + ping[2] = new OtpErlangTuple(anode); return new OtpErlangTuple(ping); } @@ -450,9 +450,8 @@ public class OtpNode extends OtpLocalNode { /* special case for netKernel requests */ if (name.equals("net_kernel")) { return netKernel(m); - } else { - mbox = mboxes.get(name); } + mbox = mboxes.get(name); } else { mbox = mboxes.get(m.getRecipientPid()); } @@ -480,23 +479,23 @@ public class OtpNode extends OtpLocalNode { /* * find or create a connection to the given node */ - OtpCookedConnection getConnection(final String node) { + OtpCookedConnection getConnection(final String anode) { OtpPeer peer = null; OtpCookedConnection conn = null; synchronized (connections) { // first just try looking up the name as-is - conn = connections.get(node); + conn = connections.get(anode); if (conn == null) { // in case node had no '@' add localhost info and try again - peer = new OtpPeer(node); + peer = new OtpPeer(anode); conn = connections.get(peer.node()); if (conn == null) { try { conn = new OtpCookedConnection(this, peer); - conn.setFlags(flags); + conn.setFlags(connFlags); addConnection(conn); } catch (final Exception e) { /* false = outgoing */ @@ -522,35 +521,35 @@ public class OtpNode extends OtpLocalNode { } /* use these wrappers to call handler functions */ - private synchronized void remoteStatus(final String node, final boolean up, + private synchronized void remoteStatus(final String anode, final boolean up, final Object info) { if (handler == null) { return; } try { - handler.remoteStatus(node, up, info); + handler.remoteStatus(anode, up, info); } catch (final Exception e) { } } - synchronized void localStatus(final String node, final boolean up, + synchronized void localStatus(final String anode, final boolean up, final Object info) { if (handler == null) { return; } try { - handler.localStatus(node, up, info); + handler.localStatus(anode, up, info); } catch (final Exception e) { } } - synchronized void connAttempt(final String node, final boolean incoming, + synchronized void connAttempt(final String anode, final boolean incoming, final Object info) { if (handler == null) { return; } try { - handler.connAttempt(node, incoming, info); + handler.connAttempt(anode, incoming, info); } catch (final Exception e) { } } @@ -684,13 +683,13 @@ public class OtpNode extends OtpLocalNode { */ public class Acceptor extends Thread { private final ServerSocket sock; - private final int port; + private final int acceptorPort; private volatile boolean done = false; Acceptor(final int port) throws IOException { sock = new ServerSocket(port); - this.port = sock.getLocalPort(); - OtpNode.this.port = this.port; + this.acceptorPort = sock.getLocalPort(); + OtpNode.this.port = this.acceptorPort; setDaemon(true); setName("acceptor"); @@ -741,7 +740,7 @@ public class OtpNode extends OtpLocalNode { } public int port() { - return port; + return acceptorPort; } @Override @@ -771,7 +770,7 @@ public class OtpNode extends OtpLocalNode { try { synchronized (connections) { conn = new OtpCookedConnection(OtpNode.this, newsock); - conn.setFlags(flags); + conn.setFlags(connFlags); addConnection(conn); } } catch (final OtpAuthException e) { @@ -802,6 +801,6 @@ public class OtpNode extends OtpLocalNode { } public void setFlags(final int flags) { - this.flags = flags; + this.connFlags = flags; } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java index c98790bbd4..ef60a9f38a 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java @@ -25,7 +25,6 @@ import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DecimalFormat; -import java.util.Arrays; import java.util.zip.Deflater; /** @@ -45,8 +44,11 @@ public class OtpOutputStream extends ByteArrayOutputStream { public static final int defaultIncrement = 2048; // static formats, used to encode floats and doubles + @SuppressWarnings("unused") private static final DecimalFormat eform = new DecimalFormat("e+00;e-00"); + @SuppressWarnings("unused") private static final BigDecimal ten = new BigDecimal(10.0); + @SuppressWarnings("unused") private static final BigDecimal one = new BigDecimal(1.0); private int fixedSize = Integer.MAX_VALUE; @@ -159,9 +161,9 @@ public class OtpOutputStream extends ByteArrayOutputStream { * @see java.io.ByteArrayOutputStream#write(byte[]) */ @Override - public void write(final byte[] buf) { + public void write(final byte[] abuf) { // don't assume that super.write(byte[]) calls write(buf, 0, buf.length) - write(buf, 0, buf.length); + write(abuf, 0, abuf.length); } /* (non-Javadoc) @@ -285,10 +287,11 @@ public class OtpOutputStream extends ByteArrayOutputStream { * @param b * the number of bytes to write from the little end. */ - public void writeLE(long n, final int b) { + public void writeLE(final long n, final int b) { + long v = n; for (int i = 0; i < b; i++) { - write((byte) (n & 0xff)); - n >>= 8; + write((byte) (v & 0xff)); + v >>= 8; } } @@ -518,16 +521,17 @@ public class OtpOutputStream extends ByteArrayOutputStream { write_double(f); } - public void write_big_integer(BigInteger v) { + public void write_big_integer(final BigInteger v) { if (v.bitLength() < 64) { this.write_long(v.longValue(), true); return; } final int signum = v.signum(); + BigInteger val = v; if (signum < 0) { - v = v.negate(); + val = val.negate(); } - final byte[] magnitude = v.toByteArray(); + final byte[] magnitude = val.toByteArray(); final int n = magnitude.length; // Reverse the array to make it little endian. for (int i = 0, j = n; i < j--; i++) { @@ -568,7 +572,7 @@ public class OtpOutputStream extends ByteArrayOutputStream { int n; long mask; for (mask = 0xFFFFffffL, n = 4; (abs & mask) != abs; n++, mask = mask << 8 | 0xffL) { - ; // count nonzero bytes + // count nonzero bytes } write1(OtpExternal.smallBigTag); write1(n); // length @@ -827,7 +831,6 @@ public class OtpOutputStream extends ByteArrayOutputStream { write_nil(); // it should never ever get here... } } else { // unicode or longer, must code as list - final char[] charbuf = s.toCharArray(); final int[] codePoints = OtpErlangString.stringToCodePoints(s); write_list_head(codePoints.length); for (final int codePoint : codePoints) { @@ -867,6 +870,7 @@ public class OtpOutputStream extends ByteArrayOutputStream { * the compression level (<tt>0..9</tt>) */ public void write_compressed(final OtpErlangObject o, int level) { + @SuppressWarnings("resource") final OtpOutputStream oos = new OtpOutputStream(o); /* * similar to erts_term_to_binary() in external.c: @@ -920,6 +924,11 @@ public class OtpOutputStream extends ByteArrayOutputStream { "Intermediate stream failed for Erlang object " + o); } finally { this.fixedSize = Integer.MAX_VALUE; + try { + dos.close(); + } catch (IOException e) { + // ignore + } } } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/jinterface.app.src b/lib/jinterface/java_src/com/ericsson/otp/erlang/jinterface.app.src new file mode 100644 index 0000000000..d25d9bc142 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/jinterface.app.src @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% This is an -*- erlang -*- file. +%% + +{application, jinterface, + [ + {description, "Jinterface"}, + {vsn, "%VSN%"}, + {modules, []}, + {registered, []}, + {applications, []}, + {env, []}, + {runtime_dependencies, []} + ] +}. diff --git a/lib/jinterface/test/jinterface_SUITE_data/FunEquals.java b/lib/jinterface/test/jinterface_SUITE_data/FunEquals.java index 14f884cee7..961e462cb3 100644 --- a/lib/jinterface/test/jinterface_SUITE_data/FunEquals.java +++ b/lib/jinterface/test/jinterface_SUITE_data/FunEquals.java @@ -17,9 +17,11 @@ * %CopyrightEnd% */ -import java.util.Arrays; - -import com.ericsson.otp.erlang.*; +import com.ericsson.otp.erlang.OtpErlangAtom; +import com.ericsson.otp.erlang.OtpErlangFun; +import com.ericsson.otp.erlang.OtpErlangLong; +import com.ericsson.otp.erlang.OtpErlangObject; +import com.ericsson.otp.erlang.OtpErlangPid; public class FunEquals { diff --git a/lib/jinterface/test/jinterface_SUITE_data/GetNames.java b/lib/jinterface/test/jinterface_SUITE_data/GetNames.java index 3d2bc4ac84..54efaad242 100644 --- a/lib/jinterface/test/jinterface_SUITE_data/GetNames.java +++ b/lib/jinterface/test/jinterface_SUITE_data/GetNames.java @@ -18,7 +18,9 @@ */ import java.util.ArrayList; -import com.ericsson.otp.erlang.*; + +import com.ericsson.otp.erlang.OtpMbox; +import com.ericsson.otp.erlang.OtpNode; class GetNames { @@ -37,7 +39,7 @@ class GetNames { OtpMbox mbox3 = node.createMbox(); node.registerName("mbox3",mbox3); - ArrayList existing_names = new ArrayList(); + ArrayList<String> existing_names = new ArrayList<String>(); existing_names.add("mbox3"); existing_names.add("mbox2"); existing_names.add("mbox1"); diff --git a/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java b/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java index 5d1d097cc8..470fdb4a14 100644 --- a/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java +++ b/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java @@ -17,7 +17,14 @@ * %CopyrightEnd% */ -import com.ericsson.otp.erlang.*; +import com.ericsson.otp.erlang.OtpErlangAtom; +import com.ericsson.otp.erlang.OtpErlangExit; +import com.ericsson.otp.erlang.OtpErlangLong; +import com.ericsson.otp.erlang.OtpErlangObject; +import com.ericsson.otp.erlang.OtpErlangPid; +import com.ericsson.otp.erlang.OtpErlangTuple; +import com.ericsson.otp.erlang.OtpMbox; +import com.ericsson.otp.erlang.OtpNode; class MboxLinkUnlink { @@ -66,7 +73,10 @@ class MboxLinkUnlink { OtpErlangObject[] msg = {mainMbox.self(),mbox.self()}; mbox.send("erl_link_server", erlNode, new OtpErlangTuple(msg)); OtpErlangObject o = mbox.receive(1000); - if (o == null) System.exit(1); + if (o == null) { + System.exit(1); + return; + } OtpErlangTuple tuple = (OtpErlangTuple)o; int tag = (int)((OtpErlangLong)tuple.elementAt(0)).longValue(); @@ -91,6 +101,7 @@ class MboxLinkUnlink { expected = tuple.elementAt(2); mbox.receive(1000); System.exit(2); + break; case erl_link_java_exit: dbg("Java got \"erl_link_java_exit\""); mbox.exit(tuple.elementAt(2)); @@ -104,6 +115,7 @@ class MboxLinkUnlink { expected = tuple.elementAt(2); mbox.receive(1000); System.exit(3); + break; case internal_link_linking_exits: dbg("Java got \"internal_link_linking_exits\""); mbox2 = node.createMbox(); @@ -113,6 +125,7 @@ class MboxLinkUnlink { expected = tuple.elementAt(2); mbox2.receive(1000); // hanging waiting for exit System.exit(4); // got someting other than exit + break; case internal_link_linked_exits: dbg("Java got \"internal_link_linked_exits\""); mbox2 = node.createMbox(); @@ -122,6 +135,7 @@ class MboxLinkUnlink { expected = tuple.elementAt(2); mbox.receive(1000); // hanging waiting for exit System.exit(5); // got someting other than exit + break; case internal_unlink_linking_exits: dbg("Java got \"internal_unlink_linking_exits\""); mbox2 = node.createMbox(); diff --git a/lib/jinterface/test/jinterface_SUITE_data/MboxSendReceive.java b/lib/jinterface/test/jinterface_SUITE_data/MboxSendReceive.java index 2db71bb5cd..44433aa619 100644 --- a/lib/jinterface/test/jinterface_SUITE_data/MboxSendReceive.java +++ b/lib/jinterface/test/jinterface_SUITE_data/MboxSendReceive.java @@ -17,7 +17,13 @@ * %CopyrightEnd% */ -import com.ericsson.otp.erlang.*; +import com.ericsson.otp.erlang.OtpErlangAtom; +import com.ericsson.otp.erlang.OtpErlangLong; +import com.ericsson.otp.erlang.OtpErlangObject; +import com.ericsson.otp.erlang.OtpErlangPid; +import com.ericsson.otp.erlang.OtpErlangTuple; +import com.ericsson.otp.erlang.OtpMbox; +import com.ericsson.otp.erlang.OtpNode; class MboxSendReceive { @@ -35,6 +41,7 @@ class MboxSendReceive { private static final int java_internal_send_receive_different_nodes = 3; private static final int java_internal_send_receive_self = 4; + @SuppressWarnings("null") public static void main(String argv[]) { String cookie = argv[0]; diff --git a/lib/jinterface/test/jinterface_SUITE_data/NodeStatusHandler.java b/lib/jinterface/test/jinterface_SUITE_data/NodeStatusHandler.java index 51ea15b5ef..06ddfa2d61 100644 --- a/lib/jinterface/test/jinterface_SUITE_data/NodeStatusHandler.java +++ b/lib/jinterface/test/jinterface_SUITE_data/NodeStatusHandler.java @@ -17,7 +17,14 @@ * %CopyrightEnd% */ -import com.ericsson.otp.erlang.*; +import com.ericsson.otp.erlang.OtpErlangAtom; +import com.ericsson.otp.erlang.OtpErlangBoolean; +import com.ericsson.otp.erlang.OtpErlangObject; +import com.ericsson.otp.erlang.OtpErlangString; +import com.ericsson.otp.erlang.OtpErlangTuple; +import com.ericsson.otp.erlang.OtpMbox; +import com.ericsson.otp.erlang.OtpNode; +import com.ericsson.otp.erlang.OtpNodeStatus; public class NodeStatusHandler extends OtpNodeStatus { /* @@ -86,7 +93,10 @@ public class NodeStatusHandler extends OtpNodeStatus { } OtpErlangObject o = mbox.receive(recTime); - if (o == null) System.exit(2); + if (o == null) { + System.exit(2); + return; + } if (! ((OtpErlangAtom)o).atomValue().equals("done")) System.exit(3); @@ -100,6 +110,7 @@ public class NodeStatusHandler extends OtpNodeStatus { + @Override public void remoteStatus(String node, boolean up, Object info) { try { dbg("Got remoteStatus: " + node + " " + up + " " + info); @@ -120,6 +131,7 @@ public class NodeStatusHandler extends OtpNodeStatus { } + @Override public void localStatus(String node, boolean up, Object info) { try { dbg("Got localStatus: " + node + " " + up + " " + info); @@ -141,6 +153,7 @@ public class NodeStatusHandler extends OtpNodeStatus { +@Override public void connAttempt(String node, boolean incoming, Object info) { try { dbg("Got connAttempt: " + node + " " + incoming + " " + info); diff --git a/lib/jinterface/test/nc_SUITE_data/echo_server.java b/lib/jinterface/test/nc_SUITE_data/echo_server.java index 2e18e908d4..0e43ea0680 100644 --- a/lib/jinterface/test/nc_SUITE_data/echo_server.java +++ b/lib/jinterface/test/nc_SUITE_data/echo_server.java @@ -148,11 +148,13 @@ public class echo_server { final String atomValue = ((OtpErlangAtom) t).atomValue(); if (atomValue.equals("binary") && i instanceof OtpErlangBinary) { final OtpErlangBinary b = (OtpErlangBinary) i; + @SuppressWarnings("resource") final OtpInputStream bis = new OtpInputStream(b.binaryValue(), 0); final OtpErlangObject o = bis.read_any(); return o; } else if (atomValue.equals("compress")) { + @SuppressWarnings("resource") final OtpOutputStream oos = new OtpOutputStream(); oos.write1(OtpExternal.versionTag); oos.write_compressed(i); @@ -206,6 +208,7 @@ public class echo_server { && i instanceof OtpErlangString) { final OtpErlangString s = (OtpErlangString) i; final String ss = s.stringValue().substring(3, 6); + @SuppressWarnings("unused") final int[] cps = OtpErlangString.stringToCodePoints(ss); return s; } else if (atomValue.equals("utf8")) { diff --git a/lib/jinterface/vsn.mk b/lib/jinterface/vsn.mk index c50200fab6..eea83c2f3f 100644 --- a/lib/jinterface/vsn.mk +++ b/lib/jinterface/vsn.mk @@ -1 +1 @@ -JINTERFACE_VSN = 1.5.9 +JINTERFACE_VSN = 1.5.11 diff --git a/lib/kernel/doc/src/error_logger.xml b/lib/kernel/doc/src/error_logger.xml index 3815b0877c..df2f0b01ee 100644 --- a/lib/kernel/doc/src/error_logger.xml +++ b/lib/kernel/doc/src/error_logger.xml @@ -58,7 +58,7 @@ specific events. (<c>add_report_handler/1,2</c>). Also, there is a useful event handler in STDLIB for multi-file logging of events, see <c>log_mf_h(3)</c>.</p> - <p>Warning events was introduced in Erlang/OTP R9C. To retain + <p>Warning events were introduced in Erlang/OTP R9C. To retain backwards compatibility, these are by default tagged as errors, thus showing up as error reports in the logs. By using the command line flag <c><![CDATA[+W <w | i>]]></c>, they can instead diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index f92770603a..7eaf2d4a44 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -30,6 +30,21 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 3.0.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Accept inet:ip_address() in net_adm:names/1</p> + <p> + Own Id: OTP-12154</p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 3.0.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index cb3c0a49f4..c7c70ad257 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -122,6 +122,7 @@ HRL_FILES= ../include/file.hrl ../include/inet.hrl ../include/inet_sctp.hrl \ ../include/net_address.hrl INTERNAL_HRL_FILES= application_master.hrl disk_log.hrl \ + erl_epmd.hrl hipe_ext_format.hrl \ inet_dns.hrl inet_res.hrl \ inet_boot.hrl inet_config.hrl inet_int.hrl \ inet_dns_record_adts.hrl diff --git a/lib/kernel/src/application_master.erl b/lib/kernel/src/application_master.erl index bc15b5a7de..7cdbe31ab2 100644 --- a/lib/kernel/src/application_master.erl +++ b/lib/kernel/src/application_master.erl @@ -103,9 +103,9 @@ call(AppMaster, Req) -> %%% The reason for not using the logical structrure is that %%% the application start function is synchronous, and %%% that the AM is GL. This means that if AM executed the start -%%% function, and this function uses spawn_request/1 -%%% or io, deadlock would occur. Therefore, this function is -%%% executed by the process X. Also, AM needs three loops; +%%% function, and this function uses io, deadlock would occur. +%%% Therefore, this function is executed by the process X. +%%% Also, AM needs three loops; %%% init_loop (waiting for the start function to return) %%% main_loop %%% terminate_loop (waiting for the process to die) diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl index a375adceea..7f6024f642 100644 --- a/lib/kernel/test/interactive_shell_SUITE.erl +++ b/lib/kernel/test/interactive_shell_SUITE.erl @@ -296,6 +296,7 @@ ctrl_keys(doc) -> ["Tests various control keys"]; ctrl_keys(_Conf) when is_list(_Conf) -> Cu=[$\^u], Cw=[$\^w], + Cy=[$\^y], Home=[27,$O,$H], End=[27,$O,$F], rtnode([{putline,""}, @@ -308,6 +309,8 @@ ctrl_keys(_Conf) when is_list(_Conf) -> {putline,"world\"."++Home++"\"hello "}, % test <HOME> {getline,"\"hello world\""}, {putline,"world"++Home++"\"hello "++End++"\"."}, % test <END> + {getline,"\"hello world\""}, + {putline,"\"hello world\""++Cu++Cy++"."}, {getline,"\"hello world\""}] ++wordLeft()++wordRight(),[]). diff --git a/lib/kernel/test/kernel_SUITE.erl b/lib/kernel/test/kernel_SUITE.erl index 1884e8cf58..613efeeb2f 100644 --- a/lib/kernel/test/kernel_SUITE.erl +++ b/lib/kernel/test/kernel_SUITE.erl @@ -79,17 +79,29 @@ appup_test(_Config) -> appup_tests(_App,{[],[]}) -> {skip,"no previous releases available"}; -appup_tests(App,{OkVsns,NokVsns}) -> +appup_tests(App,{OkVsns0,NokVsns}) -> application:load(App), {_,_,Vsn} = lists:keyfind(App,1,application:loaded_applications()), AppupFileName = atom_to_list(App) ++ ".appup", AppupFile = filename:join([code:lib_dir(App),ebin,AppupFileName]), {ok,[{Vsn,UpFrom,DownTo}=AppupScript]} = file:consult(AppupFile), ct:log("~p~n",[AppupScript]), - ct:log("Testing ok versions: ~p~n",[OkVsns]), + OkVsns = + case OkVsns0 -- [Vsn] of + OkVsns0 -> + OkVsns0; + Ok -> + ct:log("Current version, ~p, is same as in previous release.~n" + "Removing this from the list of ok versions.", + [Vsn]), + Ok + end, + ct:log("Testing that appup allows upgrade from these versions: ~p~n", + [OkVsns]), check_appup(OkVsns,UpFrom,{ok,[restart_new_emulator]}), check_appup(OkVsns,DownTo,{ok,[restart_new_emulator]}), - ct:log("Testing not ok versions: ~p~n",[NokVsns]), + ct:log("Testing that appup does not allow upgrade from these versions: ~p~n", + [NokVsns]), check_appup(NokVsns,UpFrom,error), check_appup(NokVsns,DownTo,error), ok. diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index 1ecfde190e..be633a304a 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 3.0.2 +KERNEL_VSN = 3.0.3 diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml index dff36fd51c..9a260c3878 100644 --- a/lib/megaco/doc/src/notes.xml +++ b/lib/megaco/doc/src/notes.xml @@ -36,7 +36,24 @@ section is the version number of Megaco.</p> - <section><title>Megaco 3.17.1</title> + <section><title>Megaco 3.17.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Implement --enable-sanitizers[=sanitizers]. Similar to + debugging with Valgrind, it's very useful to enable + -fsanitize= switches to catch bugs at runtime.</p> + <p> + Own Id: OTP-12153</p> + </item> + </list> + </section> + +</section> + +<section><title>Megaco 3.17.1</title> <section><title>Improvements and New Features</title> <list> diff --git a/lib/megaco/src/app/megaco.appup.src b/lib/megaco/src/app/megaco.appup.src index db59f55b55..a3a2e2ea9c 100644 --- a/lib/megaco/src/app/megaco.appup.src +++ b/lib/megaco/src/app/megaco.appup.src @@ -174,11 +174,19 @@ %% | %% v %% 3.17.0.3 +%% | +%% v +%% 3.17.1 +%% | +%% v +%% 3.17.2 %% %% {"%VSN%", [ + {"3.17.1", [{restart_application,megaco}]}, + {"3.17.0.3", [{restart_application,megaco}]}, {"3.17.0.2", []}, {"3.17.0.1", []}, {"3.17", []}, @@ -190,6 +198,8 @@ } ], [ + {"3.17.1", [{restart_application,megaco}]}, + {"3.17.0.3", [{restart_application,megaco}]}, {"3.17.0.2", []}, {"3.17.0.1", []}, {"3.17", []}, diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk index 373f5199bf..1f4e3b8e95 100644 --- a/lib/megaco/vsn.mk +++ b/lib/megaco/vsn.mk @@ -18,6 +18,6 @@ # %CopyrightEnd% APPLICATION = megaco -MEGACO_VSN = 3.17.1 +MEGACO_VSN = 3.17.2 PRE_VSN = APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)" diff --git a/lib/mnesia/doc/src/Mnesia_chap3.xml b/lib/mnesia/doc/src/Mnesia_chap3.xml index d6b4a1c6a1..ae704b4199 100644 --- a/lib/mnesia/doc/src/Mnesia_chap3.xml +++ b/lib/mnesia/doc/src/Mnesia_chap3.xml @@ -152,7 +152,7 @@ Transformer = <c>ignore</c>, it indicates that only the meta data about the table will be updated. Usage of <c>ignore</c> is not recommended (since it creates inconsistencies between the meta data and the actual data) but included - as a possibility for the user do to his own (off-line) transform.</p> + as a possibility for the user to do his own (off-line) transform.</p> </item> <item><c>change_table_copy_type(Tab, Node, ToType)</c>. This function changes the storage type of a table. For example, a diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml index 72e9bd7e8f..268dc18e65 100644 --- a/lib/mnesia/doc/src/mnesia.xml +++ b/lib/mnesia/doc/src/mnesia.xml @@ -2766,7 +2766,7 @@ raise(Name, Amount) -> new type. The <c>Fun</c> argument can also be the atom <c>ignore</c>, it indicates that only the meta data about the table will be updated. Usage of <c>ignore</c> is not recommended but included - as a possibility for the user do to his own transform. + as a possibility for the user to do his own transform. <c>NewAttributeList</c> and <c>NewRecordName</c> specifies the attributes and the new record type of converted table. Table name will always remain unchanged, if the diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml index df2c27afba..e5c7d87f52 100644 --- a/lib/mnesia/doc/src/notes.xml +++ b/lib/mnesia/doc/src/notes.xml @@ -38,7 +38,26 @@ thus constitutes one section in this document. The title of each section is the version number of Mnesia.</p> - <section><title>Mnesia 4.12.2</title> + <section><title>Mnesia 4.12.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Various logging fixes, including: Add run queue index to + the process dump in crash dumps.<br/> Add thread index to + enomem slogan when crashing.<br/> Remove error logger + message for sending messages to old instances of the same + node.</p> + <p> + Own Id: OTP-12115</p> + </item> + </list> + </section> + +</section> + +<section><title>Mnesia 4.12.2</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk index d16d501103..d5b96c5c76 100644 --- a/lib/mnesia/vsn.mk +++ b/lib/mnesia/vsn.mk @@ -1 +1 @@ -MNESIA_VSN = 4.12.2 +MNESIA_VSN = 4.12.3 diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml index c135e29520..658ac2c7cf 100644 --- a/lib/observer/doc/src/notes.xml +++ b/lib/observer/doc/src/notes.xml @@ -31,6 +31,21 @@ <p>This document describes the changes made to the Observer application.</p> +<section><title>Observer 2.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed statusbar on Windows</p> + <p> + Own Id: OTP-12162</p> + </item> + </list> + </section> + +</section> + <section><title>Observer 2.0.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml index 3aeaf1997a..62f99c5210 100644 --- a/lib/observer/doc/src/observer_ug.xml +++ b/lib/observer/doc/src/observer_ug.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2011</year><year>2013</year> + <year>2011</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -54,9 +54,6 @@ impact only the active viewer is updated and the other views will be updated when activated. </p> - <note> - <p>Only R15B nodes can be observed.</p> - </note> <p> In general the mouse buttons behaves as expected, use left click to select objects, right click to pop up a menu with most used diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index b55cff7332..dbbbde1467 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 2.0.1 +OBSERVER_VSN = 2.0.2 diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in index ea5c51965f..0cfcb9964b 100644 --- a/lib/odbc/configure.in +++ b/lib/odbc/configure.in @@ -136,7 +136,7 @@ AC_SUBST(THR_LIBS) odbc_lib_link_success=no AC_SUBST(TARGET_FLAGS) case $host_os in - darwin1[[0-2]].*|darwin[[0-9]].*) + darwin1[[0-4]].*|darwin[[0-9]].*) TARGET_FLAGS="-DUNIX" if test ! -d "$with_odbc" || test "$with_odbc" = "yes"; then ODBC_LIB= -L"/usr/lib" diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml index 0ba2b1ff3f..495a675631 100644 --- a/lib/odbc/doc/src/notes.xml +++ b/lib/odbc/doc/src/notes.xml @@ -31,7 +31,30 @@ <p>This document describes the changes made to the odbc application. </p> - <section><title>ODBC 2.10.20</title> + <section><title>ODBC 2.10.21</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix compiler warnings reported by LLVM</p> + <p> + Own Id: OTP-12138</p> + </item> + <item> + <p> + Implement --enable-sanitizers[=sanitizers]. Similar to + debugging with Valgrind, it's very useful to enable + -fsanitize= switches to catch bugs at runtime.</p> + <p> + Own Id: OTP-12153</p> + </item> + </list> + </section> + +</section> + +<section><title>ODBC 2.10.20</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk index 1af4751248..b374e42d15 100644 --- a/lib/odbc/vsn.mk +++ b/lib/odbc/vsn.mk @@ -1 +1 @@ -ODBC_VSN = 2.10.20 +ODBC_VSN = 2.10.21 diff --git a/lib/orber/doc/src/notes.xml b/lib/orber/doc/src/notes.xml index 141d306740..2167a43eee 100644 --- a/lib/orber/doc/src/notes.xml +++ b/lib/orber/doc/src/notes.xml @@ -33,7 +33,22 @@ </header> - <section><title>Orber 3.7</title> + <section><title>Orber 3.7.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fixed problem with IPv6 addresses in Service Context + when orber is default configured for IPv4. </p> + <p> + Own Id: OTP-12193</p> + </item> + </list> + </section> + +</section> + +<section><title>Orber 3.7</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/orber/src/orber_iiop_outproxy.erl b/lib/orber/src/orber_iiop_outproxy.erl index 4ba5b05995..3adb40d01a 100644 --- a/lib/orber/src/orber_iiop_outproxy.erl +++ b/lib/orber/src/orber_iiop_outproxy.erl @@ -113,7 +113,7 @@ stop(Pid) -> init({connect, Host, Port, SocketType, SocketOptions, Parent, Key, NewKey}) -> process_flag(trap_exit, true), case catch orber_socket:connect(SocketType, Host, Port, - get_ip_family_opts(Host) ++ SocketOptions) of + orber_socket:get_ip_family_opts(Host) ++ SocketOptions) of {'EXCEPTION', _E} -> ignore; %% We used to reply the below but since this would generate a CRASH REPORT @@ -508,38 +508,3 @@ clear_queue(Proxy, RequestId, MRef) -> end end. -get_ip_family_opts(Host) -> - case inet:parse_address(Host) of - {ok, {_,_,_,_}} -> - [inet]; - {ok, {_,_,_,_,_,_,_,_}} -> - [inet6]; - {error, einval} -> - check_family_for_name(Host, orber_env:ip_version()) - end. - -check_family_for_name(Host, inet) -> - case inet:getaddr(Host, inet) of - {ok, _Address} -> - [inet]; - {error, _} -> - case inet:getaddrs(Host, inet6) of - {ok, _Address} -> - [inet6]; - {error, _} -> - [inet] - end - end; -check_family_for_name(Host, inet6) -> - case inet:getaddr(Host, inet6) of - {ok, _Address} -> - [inet6]; - {error, _} -> - case inet:getaddr(Host, inet) of - {ok, _Address} -> - [inet]; - {error, _} -> - [inet6] - end - end. - diff --git a/lib/orber/src/orber_iiop_pm.erl b/lib/orber/src/orber_iiop_pm.erl index 927d12b3b2..fee2354f11 100644 --- a/lib/orber/src/orber_iiop_pm.erl +++ b/lib/orber/src/orber_iiop_pm.erl @@ -775,12 +775,11 @@ do_setup_connection(PMPid, Host, Port, SocketType, SocketOptions, Chars, access_allowed(Host, Port, Type, {_,_,UserInterface}) -> Flags = orber:get_flags(), - Family = orber_env:ip_version(), case ?ORB_FLAG_TEST(Flags, ?ORB_ENV_USE_ACL_OUTGOING) of false when UserInterface == 0 -> - get_local_interface(Type, Family); + get_local_interface(Type); false -> - inet:getaddr(UserInterface, Family); + inet:getaddr(UserInterface, get_ip_family(UserInterface)); true -> SearchFor = case Type of @@ -789,43 +788,48 @@ access_allowed(Host, Port, Type, {_,_,UserInterface}) -> ssl -> ssl_out end, - {ok, Ip} = inet:getaddr(Host, Family), + {ok, Ip} = inet:getaddr(Host, get_ip_family(Host)), case orber_acl:match(Ip, SearchFor, true) of {true, [], 0} -> - get_local_interface(Type, Family); + get_local_interface(Type); {true, [], Port} -> - get_local_interface(Type, Family); + get_local_interface(Type); {true, [], {Min, Max}} when Port >= Min, Port =< Max -> - get_local_interface(Type, Family); - {true, [Interface], 0} -> - {ok, NewIp} = inet:getaddr(Interface, Family), + get_local_interface(Type); + {true, [Interface], 0} -> + {ok, NewIp} = inet:getaddr(Interface, get_ip_family(Interface)), {ok, NewIp, {Host, Port, 0}}; {true, [Interface], Port} -> - {ok, NewIp} = inet:getaddr(Interface, Family), + + {ok, NewIp} = inet:getaddr(Interface, get_ip_family(Interface)), {ok, NewIp, {Host, Port, 0}}; {true, [Interface], {Min, Max}} when Port >= Min, Port =< Max -> - {ok, NewIp} = inet:getaddr(Interface, Family), + + {ok, NewIp} = inet:getaddr(Interface, get_ip_family(Interface)), {ok, NewIp, {Host, Port, 0}}; _ -> false end end. -get_local_interface(normal, Family) -> +get_local_interface(normal) -> case orber_env:ip_address_local() of [] -> ok; [Interface] -> - inet:getaddr(Interface, Family) + inet:getaddr(Interface, get_ip_family(Interface)) end; -get_local_interface(ssl, Family) -> +get_local_interface(ssl) -> case orber_env:iiop_ssl_ip_address_local() of [] -> ok; [Interface] -> - inet:getaddr(Interface, Family) + inet:getaddr(Interface, get_ip_family(Interface)) end. +get_ip_family(Addr) -> + [Family] = orber_socket:get_ip_family_opts(Addr), + Family. invoke_connection_closed(false) -> ok; diff --git a/lib/orber/src/orber_socket.erl b/lib/orber/src/orber_socket.erl index c8d2f0636b..4507d90cce 100644 --- a/lib/orber/src/orber_socket.erl +++ b/lib/orber/src/orber_socket.erl @@ -37,7 +37,8 @@ -export([start/0, connect/4, listen/3, listen/4, accept/2, accept/3, write/3, controlling_process/3, close/2, peername/2, sockname/2, peerdata/2, peercert/2, sockdata/2, setopts/3, - clear/2, shutdown/3, post_accept/2, post_accept/3]). + clear/2, shutdown/3, post_accept/2, post_accept/3, + get_ip_family_opts/1]). %%----------------------------------------------------------------- %% Internal exports @@ -491,4 +492,40 @@ check_options(ssl, Options, Generation) -> end. - +%%----------------------------------------------------------------- +%% Check IP Family. +get_ip_family_opts(Host) -> + case inet:parse_address(Host) of + {ok, {_,_,_,_}} -> + [inet]; + {ok, {_,_,_,_,_,_,_,_}} -> + [inet6]; + {error, einval} -> + check_family_for_name(Host, orber_env:ip_version()) + end. + +check_family_for_name(Host, inet) -> + case inet:getaddr(Host, inet) of + {ok, _Address} -> + [inet]; + {error, _} -> + case inet:getaddr(Host, inet6) of + {ok, _Address} -> + [inet6]; + {error, _} -> + [inet] + end + end; +check_family_for_name(Host, inet6) -> + case inet:getaddr(Host, inet6) of + {ok, _Address} -> + [inet6]; + {error, _} -> + case inet:getaddr(Host, inet) of + {ok, _Address} -> + [inet]; + {error, _} -> + [inet6] + end + end. + diff --git a/lib/orber/vsn.mk b/lib/orber/vsn.mk index 13bdf55c07..28fe9323fb 100644 --- a/lib/orber/vsn.mk +++ b/lib/orber/vsn.mk @@ -1,2 +1 @@ -ORBER_VSN = 3.7 - +ORBER_VSN = 3.7.1 diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml index 5ac04d4f42..6bc0cf7d43 100644 --- a/lib/os_mon/doc/src/notes.xml +++ b/lib/os_mon/doc/src/notes.xml @@ -30,6 +30,23 @@ </header> <p>This document describes the changes made to the OS_Mon application.</p> +<section><title>Os_Mon 2.3</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Adds a new application parameter 'disksup_posix_only', to + make diskup use only options defined in the POSIX + standard.</p> + <p> + Own Id: OTP-12053</p> + </item> + </list> + </section> + +</section> + <section><title>Os_Mon 2.2.15</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk index 74397c2bc6..f90cc306f0 100644 --- a/lib/os_mon/vsn.mk +++ b/lib/os_mon/vsn.mk @@ -1 +1 @@ -OS_MON_VSN = 2.2.15 +OS_MON_VSN = 2.3 diff --git a/lib/ose/doc/src/notes.xml b/lib/ose/doc/src/notes.xml index 760b92feed..7e86355f98 100644 --- a/lib/ose/doc/src/notes.xml +++ b/lib/ose/doc/src/notes.xml @@ -30,4 +30,63 @@ </header> <p>This document describes the changes made to the OSE application.</p> +<section><title>Ose 1.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Add missing release notes for the OSE application.</p> + <p> + Own Id: OTP-12177</p> + </item> + </list> + </section> + +</section> + +<section><title>Ose 1.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix some spelling mistakes in documentation</p> + <p> + Own Id: OTP-12152</p> + </item> + </list> + </section> + +</section> + +<section><title>Ose 1.0</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Erlang/OTP has been ported to the realtime operating + system OSE. The port supports both smp and non-smp + emulator. For details around the port and how to started + see the User's Guide in the <seealso + marker="ose:ose_intro">ose</seealso> application. </p> + <p> + Note that not all parts of Erlang/OTP has been ported. </p> + <p> + Notable things that work are: non-smp and smp emulators, + OSE signal interaction, crypto, asn1, run_erl/to_erl, + tcp, epmd, distribution and most if not all non-os + specific functionality of Erlang.</p> + <p> + Notable things that does not work are: udp/sctp, os_mon, + erl_interface, binding of schedulers.</p> + <p> + Own Id: OTP-11334</p> + </item> + </list> + </section> + +</section> + </chapter> diff --git a/lib/ose/vsn.mk b/lib/ose/vsn.mk index 78ffa4d496..70db2ed69d 100644 --- a/lib/ose/vsn.mk +++ b/lib/ose/vsn.mk @@ -1 +1 @@ -OSE_VSN = 1.0 +OSE_VSN = 1.0.2 diff --git a/lib/otp_mibs/src/Makefile b/lib/otp_mibs/src/Makefile index 4f03d0228a..6096240bbd 100644 --- a/lib/otp_mibs/src/Makefile +++ b/lib/otp_mibs/src/Makefile @@ -72,7 +72,7 @@ ERL_COMPILE_FLAGS += -I$(INCLUDE) +warn_obsolete_guard debug opt: $(TARGETS) clean: - rm -f $(TARGETS_FILES) + rm -f $(TARGET_FILES) rm -f $(APP_TARGET) rm -f $(APPUP_TARGET) rm -f core diff --git a/lib/parsetools/include/leexinc.hrl b/lib/parsetools/include/leexinc.hrl index dbbb688d2d..938aef58f9 100644 --- a/lib/parsetools/include/leexinc.hrl +++ b/lib/parsetools/include/leexinc.hrl @@ -36,8 +36,8 @@ string(Ics0, L0, Tcs, Ts) -> string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L0), Ts); {reject,_Alen,Tlen,_Ics1,L1,_S1} -> % After a non-accepting state {error,{L0,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; - {A,Alen,_Tlen,_Ics1,L1,_S1} -> - string_cont(yysuf(Tcs, Alen), L1, yyaction(A, Alen, Tcs, L0), Ts) + {A,Alen,_Tlen,_Ics1,_L1,_S1} -> + string_cont(yysuf(Tcs, Alen), L0, yyaction(A, Alen, Tcs, L0), Ts) end. %% string_cont(RestChars, Line, Token, Tokens) @@ -105,8 +105,8 @@ token(S0, Ics0, L0, Tcs, Tlen0, Tline, A0, Alen0) -> {reject,_Alen1,Tlen1,Ics1,L1,_S1} -> % No token match Error = {Tline,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}, {done,{error,Error,L1},Ics1}; - {A1,Alen1,_Tlen1,_Ics1,L1,_S1} -> % Use last accept match - token_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, Tline)) + {A1,Alen1,_Tlen1,_Ics1,_L1,_S1} -> % Use last accept match + token_cont(yysuf(Tcs, Alen1), L0, yyaction(A1, Alen1, Tcs, Tline)) end. %% token_cont(RestChars, Line, Token) @@ -177,9 +177,9 @@ tokens(S0, Ics0, L0, Tcs, Tlen0, Tline, Ts, A0, Alen0) -> %% Skip rest of tokens. Error = {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}, skip_tokens(yysuf(Tcs, Tlen1+1), L1, Error); - {A1,Alen1,_Tlen1,_Ics1,L1,_S1} -> + {A1,Alen1,_Tlen1,_Ics1,_L1,_S1} -> Token = yyaction(A1, Alen1, Tcs, Tline), - tokens_cont(yysuf(Tcs, Alen1), L1, Token, Ts) + tokens_cont(yysuf(Tcs, Alen1), L0, Token, Ts) end. %% tokens_cont(RestChars, Line, Token, Tokens) diff --git a/lib/parsetools/test/leex_SUITE.erl b/lib/parsetools/test/leex_SUITE.erl index eb15bebf63..6d2afe061e 100644 --- a/lib/parsetools/test/leex_SUITE.erl +++ b/lib/parsetools/test/leex_SUITE.erl @@ -43,8 +43,8 @@ file/1, compile/1, syntax/1, pt/1, man/1, ex/1, ex2/1, not_yet/1, - - otp_10302/1, otp_11286/1, unicode/1]). + line_wrap/1, + otp_10302/1, otp_11286/1, unicode/1]). % Default timetrap timeout (set in init_per_testcase). -define(default_timeout, ?t:minutes(1)). @@ -61,12 +61,13 @@ end_per_testcase(_Case, Config) -> suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [{group, checks}, {group, examples}]. + [{group, checks}, {group, examples}, {group, bugs}]. groups() -> [{checks, [], [file, compile, syntax]}, {examples, [], [pt, man, ex, ex2, not_yet, unicode]}, - {tickets, [], [otp_10302, otp_11286]}]. + {tickets, [], [otp_10302, otp_11286]}, + {bugs, [], [line_wrap]}]. init_per_suite(Config) -> Config. @@ -871,6 +872,48 @@ scan_token_1({more, Cont}, [C | Cs], Fun, Loc, Rs) -> %% End of ex2 +line_wrap(doc) -> "Much more examples."; +line_wrap(suite) -> []; +line_wrap(Config) when is_list(Config) -> + Xrl = + <<" +Definitions. +Rules. +[a]+[\\n]*= : {token, {first, TokenLine}}. +[a]+ : {token, {second, TokenLine}}. +[\\s\\r\\n\\t]+ : skip_token. +Erlang code. + ">>, + Dir = ?privdir, + XrlFile = filename:join(Dir, "test_line_wrap.xrl"), + ?line ok = file:write_file(XrlFile, Xrl), + ErlFile = filename:join(Dir, "test_line_wrap.erl"), + {ok, _} = leex:file(XrlFile, []), + {ok, _} = compile:file(ErlFile, [{outdir,Dir}]), + code:purge(test_line_wrap), + AbsFile = filename:rootname(ErlFile, ".erl"), + code:load_abs(AbsFile, test_line_wrap), + fun() -> + S = "aaa\naaa", + {ok,[{second,1},{second,2}],2} = test_line_wrap:string(S) + end(), + fun() -> + S = "aaa\naaa", + {ok,[{second,3},{second,4}],4} = test_line_wrap:string(S, 3) + end(), + fun() -> + {done,{ok,{second,1},1},"\na"} = test_line_wrap:token([], "a\na"), + {more,Cont1} = test_line_wrap:token([], "\na"), + {done,{ok,{second,2},2},eof} = test_line_wrap:token(Cont1, eof) + end(), + fun() -> + {more,Cont1} = test_line_wrap:tokens([], "a\na"), + {done,{ok,[{second,1},{second,2}],2},eof} = test_line_wrap:tokens(Cont1, eof) + end(), + ok. + +%% End of line_wrap + not_yet(doc) -> "Not yet implemented."; not_yet(suite) -> []; diff --git a/lib/percept/src/Makefile b/lib/percept/src/Makefile index 6bf0af9dc6..0282d6346a 100644 --- a/lib/percept/src/Makefile +++ b/lib/percept/src/Makefile @@ -50,6 +50,8 @@ MODULES= \ #HRL_FILES= ../include/ +INTERNAL_HRL_FILES= egd.hrl percept.hrl + ERL_FILES= $(MODULES:%=%.erl) TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) @@ -95,6 +97,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt $(INSTALL_DIR) "$(RELSYSDIR)/src" $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)/src" + $(INSTALL_DATA) $(INTERNAL_HRL_FILES) "$(RELSYSDIR)/src" # $(INSTALL_DIR) "$(RELSYSDIR)/include" # $(INSTALL_DATA) $(HRL_FILES) "$(RELSYSDIR)/include" $(INSTALL_DIR) "$(RELSYSDIR)/ebin" diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml index 592d3c797d..fe4bf5ce2d 100644 --- a/lib/public_key/doc/src/notes.xml +++ b/lib/public_key/doc/src/notes.xml @@ -34,6 +34,22 @@ <file>notes.xml</file> </header> +<section><title>Public_Key 0.22.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Added missing encoding support for PBES2, and also + completed support for PBES1 that was incomplete.</p> + <p> + Own Id: OTP-11915</p> + </item> + </list> + </section> + +</section> + <section><title>Public_Key 0.22</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 88b1a9248e..e3473f80d7 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -431,10 +431,12 @@ <name>pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name> <fsummary> Performs a basic path validation according to RFC 5280.</fsummary> <type> - <v> TrustedCert = #'OTPCertificate'{} | der_encode() | unknown_ca | selfsigned_peer </v> - <d>Normally a trusted certificate but it can also be one of the path validation - errors <c>unknown_ca </c> or <c>selfsigned_peer </c> that can be discovered while - constructing the input to this function and that should be run through the <c>verify_fun</c>.</d> + <v> TrustedCert = #'OTPCertificate'{} | der_encode() | atom() </v> + <d>Normally a trusted certificate but it can also be a path validation + error that can be discovered while + constructing the input to this function and that should be run through the <c>verify_fun</c>. + For example <c>unknown_ca </c> or <c>selfsigned_peer </c> + </d> <v> CertChain = [der_encode()]</v> <d>A list of DER encoded certificates in trust order ending with the peer certificate.</d> <v> Options = proplists:proplist()</v> @@ -442,8 +444,8 @@ rsa_public_key() | integer(), 'NULL' | 'Dss-Parms'{}}</v> <v> PolicyTree = term() </v> <d>At the moment this will always be an empty list as Policies are not currently supported</d> - <v> Reason = cert_expired | invalid_issuer | invalid_signature | unknown_ca | - selfsigned_peer | name_not_permitted | missing_basic_constraint | invalid_key_usage | crl_reason() + <v> Reason = cert_expired | invalid_issuer | invalid_signature | name_not_permitted | + missing_basic_constraint | invalid_key_usage | {revoked, crl_reason()} | atom() </v> </type> <desc> @@ -451,7 +453,7 @@ Performs a basic path validation according to <url href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280.</url> However CRL validation is done separately by <seealso - marker="public_key#pkix_crls_validate-3">pkix_crls_validate/3 </seealso> and should be called + marker="#pkix_crls_validate-3">pkix_crls_validate/3 </seealso> and should be called from the supplied <c>verify_fun</c> </p> @@ -464,7 +466,7 @@ <code> fun(OtpCert :: #'OTPCertificate'{}, - Event :: {bad_cert, Reason :: atom()} | + Event :: {bad_cert, Reason :: atom() | {revoked, atom()}} | {extension, #'Extension'{}}, InitialUserState :: term()) -> {valid, UserState :: term()} | @@ -493,6 +495,35 @@ fun(OtpCert :: #'OTPCertificate'{}, on. </item> </taglist> + + <p> Possible reasons for a bad certificate are: </p> + <taglist> + <tag>cert_expired</tag> + <item>The certificate is no longer valid as its expiration date has passed.</item> + + <tag>invalid_issuer</tag> + <item>The certificate issuer name does not match the name of the issuer certificate in the chain.</item> + + <tag>invalid_signature</tag> + <item>The certificate was not signed by its issuer certificate in the chain.</item> + + <tag>name_not_permitted</tag> + <item>Invalid Subject Alternative Name extension.</item> + + <tag>missing_basic_constraint</tag> + <item>Certificate, required to have the basic constraints extension, does not have + a basic constraints extension.</item> + + <tag>invalid_key_usage</tag> + <item>Certificate key is used in an invalid way according to the key usage extension.</item> + + <tag>{revoked, crl_reason()}</tag> + <item>Certificate has been revoked.</item> + + <tag>atom()</tag> + <item>Application specific error reason that should be checked by the verify_fun</item> + </taglist> + </desc> </func> @@ -508,7 +539,7 @@ fun(OtpCert :: #'OTPCertificate'{}, </type> <desc> <p> Performs CRL validation. It is intended to be called from - the verify fun of <seealso marker="public_key#pkix_path_validation-3"> pkix_path_validation/3 + the verify fun of <seealso marker="#pkix_path_validation-3"> pkix_path_validation/3 </seealso></p> <taglist> <p> Available options are: </p> diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index f0450918aa..2fa2d725c3 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1 +1 @@ -PUBLIC_KEY_VSN = 0.22 +PUBLIC_KEY_VSN = 0.22.1 diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml index 2928a12d22..95d7c6fa50 100644 --- a/lib/sasl/doc/src/notes.xml +++ b/lib/sasl/doc/src/notes.xml @@ -30,6 +30,26 @@ </header> <p>This document describes the changes made to the SASL application.</p> +<section><title>SASL 2.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The documentation erroneously specified that + <c>alarm_handler:clear_alarm/1</c> would clear + <em>all</em> alarms with id <c>AlarmId</c>. This is now + corrected according to the implementation - only the + latest received alarm with the given <c>AlarmId</c> is + cleared by the simple default handler.</p> + <p> + Own Id: OTP-12025</p> + </item> + </list> + </section> + +</section> + <section><title>SASL 2.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/sasl/test/sasl_SUITE.erl b/lib/sasl/test/sasl_SUITE.erl index e91d220daf..d7b99d506e 100644 --- a/lib/sasl/test/sasl_SUITE.erl +++ b/lib/sasl/test/sasl_SUITE.erl @@ -57,17 +57,29 @@ appup_test(_Config) -> appup_tests(_App,{[],[]}) -> {skip,"no previous releases available"}; -appup_tests(App,{OkVsns,NokVsns}) -> +appup_tests(App,{OkVsns0,NokVsns}) -> application:load(App), {_,_,Vsn} = lists:keyfind(App,1,application:loaded_applications()), AppupFileName = atom_to_list(App) ++ ".appup", AppupFile = filename:join([code:lib_dir(App),ebin,AppupFileName]), {ok,[{Vsn,UpFrom,DownTo}=AppupScript]} = file:consult(AppupFile), ct:log("~p~n",[AppupScript]), - ct:log("Testing ok versions: ~p~n",[OkVsns]), + OkVsns = + case OkVsns0 -- [Vsn] of + OkVsns0 -> + OkVsns0; + Ok -> + ct:log("Current version, ~p, is same as in previous release.~n" + "Removing this from the list of ok versions.", + [Vsn]), + Ok + end, + ct:log("Testing that appup allows upgrade from these versions: ~p~n", + [OkVsns]), check_appup(OkVsns,UpFrom,{ok,[restart_new_emulator]}), check_appup(OkVsns,DownTo,{ok,[restart_new_emulator]}), - ct:log("Testing not ok versions: ~p~n",[NokVsns]), + ct:log("Testing that appup does not allow upgrade from these versions: ~p~n", + [NokVsns]), check_appup(NokVsns,UpFrom,error), check_appup(NokVsns,DownTo,error), ok. diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk index da8cbc5130..4259a2d76c 100644 --- a/lib/sasl/vsn.mk +++ b/lib/sasl/vsn.mk @@ -1 +1 @@ -SASL_VSN = 2.4 +SASL_VSN = 2.4.1 diff --git a/lib/snmp/.gitignore b/lib/snmp/.gitignore index 650c1d6865..aef73491a4 100644 --- a/lib/snmp/.gitignore +++ b/lib/snmp/.gitignore @@ -5,5 +5,4 @@ *.rej doc/index.html - - +mibs/.index diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml index 15efd47a1c..bbe6438f04 100644 --- a/lib/snmp/doc/src/notes.xml +++ b/lib/snmp/doc/src/notes.xml @@ -33,7 +33,24 @@ </header> - <section><title>SNMP 5.0</title> + <section><title>SNMP 5.1</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + The SNMP manager has been enhanced with dual stack + IPv4+IPv6, as the agent just was. The documentation is + also now updated for both the agent and the manager.</p> + <p> + Own Id: OTP-12108 Aux Id: OTP-12020 </p> + </item> + </list> + </section> + +</section> + +<section><title>SNMP 5.0</title> <section><title>Improvements and New Features</title> <list> diff --git a/lib/snmp/doc/src/snmp_agent_config_files.xml b/lib/snmp/doc/src/snmp_agent_config_files.xml index 1e8e879814..1e938c0dc8 100644 --- a/lib/snmp/doc/src/snmp_agent_config_files.xml +++ b/lib/snmp/doc/src/snmp_agent_config_files.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>1997</year><year>2013</year> + <year>1997</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -102,20 +102,41 @@ <p><c>AgentVariable</c> is one of the variables is SNMP-FRAMEWORK-MIB or one of the internal variables <c>intAgentUDPPort</c>, which defines which UDP port the agent - listens to, or <c>intAgentIpAddress</c>, which defines the IP - address of the agent. </p> + listens to, or <c>intAgentTransports</c>, which defines the + transport domains and addresses of the agent. </p> </item> <item> <p><c>Value</c> is the value for the variable.</p> </item> </list> - <p>The following example shows a <c>agent.conf</c> file: </p> + <p>The following example shows an <c>agent.conf</c> file: </p> <pre> {intAgentUDPPort, 4000}. -{intAgentIpAddress,[141,213,11,24]}. +{intAgentTransports, + [{transportDomainUdpIpv4, {141,213,11,24}}, + {transportDomainUdpIpv6, {0,0,0,0,0,0,0,1}}]}. {snmpEngineID, "mbj's engine"}. {snmpEngineMaxPacketSize, 484}. </pre> + <p>The value of <c>intAgentTransports</c> is a list of + <c>{Domain, Addr}</c> tuples, where <c>Domain</c> + is either <c>transportDomainUdpIpv4</c> or <c>transportDomainUdpIpv6</c>, + and <c>Addr</c> is the address in the domain. + <c>Addr</c> can be specified either as an + <c>IpAddr</c> or as an <c>{IpAddr, IpPort}</c> tuple. + <c>IpAddr</c> is either a regular Erlang/OTP + <seealso marker="kernel:inet#type-ip_address"><c>ip_address()</c></seealso> + or a traditional SNMP integer list and <c>IpPort</c> is an integer. + </p> + + <p>When the <c>Addr</c> value does not contain a port number, + the value of <c>intAgentUDPPort</c> is used.</p> + + <p>The legacy and intermediate variables <c>intAgentIpAddress</c> + and <c>intAgentTransportDomain</c> are still supported so old + <c>agent.conf</c> files will work. + </p> + <p>The value of <c>snmpEngineID</c> is a string, which for a deployed agent should have a very specific structure. See RFC 2271/2571 for details.</p> @@ -362,9 +383,9 @@ SNMP-TARGET-MIB and <c>snmpTargetAddrExtTable</c> in the SNMP-COMMUNITY-MIB. </p> <p>Each entry is a term: </p> - <p><c>{TargetName, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId}.</c> <br></br> or <br></br> -<c>{TargetName, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize}.</c> <br></br> or <br></br> -<c>{TargetName, Domain, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize}.</c> </p> + <p><c>{TargetName, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId}.</c> + <br></br> or <br></br> + <c>{TargetName, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize}.</c> </p> <list type="bulleted"> <item> <p><c>TargetName</c> is a unique non-empty string. </p> @@ -374,11 +395,14 @@ <c>transportDomainUdpIpv4</c> | <c>transportDomainUdpIpv6</c>. </p> </item> <item> - <p><c>Ip</c> is a list of four or eight integers. </p> - </item> - <item> - <p><c>Udp</c> is an integer. </p> + <p><c>Addr</c> is either an <c>IpAddr</c> or + an <c>{IpAddr, IpPort}</c> tuple. <c>IpAddr</c> is either + a regular Erlang/OTP + <seealso marker="kernel:inet#type-ip_address"><c>ip_address()</c></seealso> + or a traditional SNMP integer list, and <c>IpPort</c> is an integer.</p> + <p>If <c>IpPort</c> is omitted <c>162</c> is used.</p> </item> + <item> <p><c>Timeout</c> is an integer. </p> </item> @@ -395,13 +419,17 @@ <p><c>EngineId</c> is a string or the atom <c>discovery</c>. </p> </item> <item> - <p><c>TMask</c> is a list of integer() of size 0, - size 6 or size 10 (default: []). </p> + <p><c>TMask</c> is specified just as <c>Addr</c> or as <c>[]</c>. + Note in particular that using a list of 6 bytes for IPv4 + or 8 words plus 2 bytes for IPv6 are still valid address formats + so old configurations will work.</p> </item> <item> <p><c>MaxMessageSize</c> is an integer (default: 2048). </p> </item> </list> + <p>The old tuple formats with <c>Ip</c> address and <c>Udp</c> + port number found in old configurations still work.</p> <p>Note that if <c>EngineId</c> has the value <c>discovery</c>, the agent cannot send <c>inform</c> messages to that manager until it has performed the diff --git a/lib/snmp/doc/src/snmp_agent_netif.xml b/lib/snmp/doc/src/snmp_agent_netif.xml index fccfc8857a..a9ce05e757 100644 --- a/lib/snmp/doc/src/snmp_agent_netif.xml +++ b/lib/snmp/doc/src/snmp_agent_netif.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>1997</year><year>2013</year> + <year>1997</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -51,7 +51,8 @@ </p> <p>It is also possible to write your own Net if process. The default Net if process is implemented in the module <c>snmpa_net_if</c> and - it uses UDP as the transport protocol. + it uses UDP as the transport protocol i.e the transport domains + <c>transportDomainUdpIpv4</c> and/or <c>transportDomainUdpIpv6</c>. </p> <p>This section describes how to write a Net if process. </p> @@ -70,6 +71,12 @@ <p>The section <em>Messages</em> describes mandatory messages, which Net if must send and be able to receive. </p> + <p>In this section an <c>Address</c> field is a + <c>{Domain, Addr}</c> tuple where <c>Domain</c> is + <c>transportDomainUdpIpv4</c> or <c>transportDomainUdpIpv4</c>, + and <c>Addr</c> is an + <c>{<seealso marker="kernel:inet#type-ip_address">IpAddr</seealso>, + IpPort}</c> tuple.</p> <section> <marker id="outgoing_messages"></marker> @@ -96,10 +103,7 @@ MasterAgent ! {snmp_pdu, Vsn, Pdu, PduMS, ACMData, From, Extra} in use. Normally this is returned from <c>snmpa_mpd:process_packet</c> (see Reference Manual). </item> - <item><c>From</c> is the source address. If UDP over IP is - used, this should be a 2-tuple <c>{IP, UDPport}</c>, where - <c>IP</c> is a 4-tuple with the IP address, and <c>UDPport</c> - is an integer. + <item><c>From</c> is the source <c>Address</c>. </item> <item><c>Extra</c> is any term the Net if process wishes to send to the agent. This term can be retrieved by the @@ -127,10 +131,7 @@ Pid ! {snmp_response_received, Vsn, Pdu, From} </item> <item><c>Pdu</c> is the SNMP Pdu received </item> - <item><c>From</c> is the source address. If UDP over IP is - used, this should be a 2-tuple <c>{IP, UDPport}</c>, where - <c>IP</c> is a 4-tuple with the IP address, and <c>UDPport</c> - is an integer. + <item><c>From</c> is the source <c>Address</c>. </item> </list> </section> @@ -168,10 +169,9 @@ Pid ! {snmp_response_received, Vsn, Pdu, From} (see Reference Manual). </p> </item> <item> - <p><c>To</c> is the destination address. If UDP over IP - is used, this should be a 2-tuple <c>{IP, UDPport}</c>, - where <c>IP</c> is a 4-tuple with the IP address, and - <c>UDPport</c> is an integer. </p> + <p><c>To</c> is the destination <c>Address</c> that comes + from the <c>From</c> field in the corresponding <c>snmp_pdu</c> + message previously sent to the MasterAgent.</p> </item> <item> <p><c>Extra</c> is the term that the Net if process @@ -230,7 +230,8 @@ Pid ! {snmp_response_received, Vsn, Pdu, From} SNMPv3, it is the context information. </p> </item> <item> - <p><c>To</c> is a list of the destination addresses and + <p><c>To</c> is a list of <c>{Address, SecData}</c> + tuples i.e the destination addresses and their corresponding security parameters. This value is normally sent to <c>snmpa_mpd:generate_message/4</c>. </p> </item> @@ -268,7 +269,8 @@ Pid ! {snmp_response_received, Vsn, Pdu, From} SNMPv3, it is the context information. </p> </item> <item> - <p><c>To</c> is a list of the destination addresses and + <p><c>To</c> is a list of <c>{Address, SecData}</c> + tuples i.e the destination addresses and their corresponding security parameters. This value is normally sent to <c>snmpa_mpd:generate_message/4</c>. </p> </item> diff --git a/lib/snmp/doc/src/snmp_manager_config_files.xml b/lib/snmp/doc/src/snmp_manager_config_files.xml index 486ef7c170..d8bd4b0f3a 100644 --- a/lib/snmp/doc/src/snmp_manager_config_files.xml +++ b/lib/snmp/doc/src/snmp_manager_config_files.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -64,13 +64,42 @@ <item> <p><c>Variable</c> is one of the following:</p> <list type="bulleted"> - <item> - <p><c>address</c> - which defines the IP address of the - manager. Default is local host.</p> - </item> + <item> + <p><c>transports</c> - which defines the transport domains + and their addresses for the manager. <em>Mandatory</em> + </p> + <p><c>Value</c> is a list of <c>{Domain, Addr}</c> tuples + or <c>Domain</c> atoms. + </p> + <list type="bulleted"> + <item> + <p><c>Domain</c> is one of <c>transportDomainUdpIpv4</c> + or <c>transportDomainUdpIpv6</c>.</p> + </item> + <item> + <p><c>Addr</c> is for the currently supported domains + either an <c>IpAddr</c> or an <c>{IpAddr, IpPort}</c> + tuple.<c>IpAddr</c> is either a regular Erlang/OTP + <seealso marker="kernel:inet#type-ip_address"> + <c>ip_address()</c></seealso> or a traditional SNMP integer list + and <c>IpPort</c> is an integer. + </p> + <p>When <c>Addr</c> does not contain a port number, + the value of <c>port</c> is used. + </p> + <p>When a <c>Addr</c> is not specified i.e by + using only a <c>Domain</c> atom, the host's name + is resolved to find the IP address, and the value of + <c>port</c> is used. + </p> + </item> + </list> + </item> <item> <p><c>port</c> - which defines which UDP port the manager uses - for communicating with agents. <em>Mandatory</em>.</p> + for communicating with agents. + <em>Mandatory</em> if <c>transports</c> does not define + a port number for every transport.</p> </item> <item> <p><c>engine_id</c> - The <c>SnmpEngineID</c> as defined in @@ -87,11 +116,13 @@ </p> </item> </list> + <p>The legacy and intermediate variables <c>address</c> and <c>domain</c> + are still supported so old configurations will work.</p> <p>The following example shows a <c>manager.conf</c> file: </p> <pre> -{address, [141,213,11,24]}. -{port, 5000}. +{transports, [{transportDomainUdpIpv4, {{141,213,11,24}, 5000}}, + {transportDomainUdpIpv6, {{0,0,0,0,0,0,0,1}, 5000}}]}. {engine_id, "mgrEngine"}. {max_message_size, 484}. </pre> @@ -146,7 +177,7 @@ </p> <p>Each entry is a tuple: </p> - <p><c>{UserId, TargetName, Comm, Ip, Port, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel}.</c></p> + <p><c>{UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel}.</c></p> <list type="bulleted"> <item> <p><c>UserId</c> is the identity of the <em>manager user</em> @@ -160,10 +191,17 @@ <p><c>Comm</c> is the community string (string).</p> </item> <item> - <p><c>Ip</c> is the ip address of the agent (a list of four integers).</p> + <p><c>Domain</c> is the transport domain, either + <c>transportDomainUdpIpv4</c> or <c>transportDomainUdpIpv6</c>.</p> </item> <item> - <p><c>Port</c> is the port number of the agent (integer).</p> + <p><c>Addr</c> is the address in the transport domain, + either an <c>{IpAddr, IpPort}</c> tuple or a traditional SNMP + integer list containing port number. <c>IpAddr</c> is either + a regular Erlang/OTP + <seealso marker="kernel:inet#type-ip_address"><c>ip_address()</c></seealso> + or a traditional SNMP integer list not containing port number, + and <c>IpPort</c> is an integer.</p> </item> <item> <p><c>EngineID</c> is the engine-id of the agent (string).</p> @@ -190,6 +228,9 @@ authPriv).</p> </item> </list> + <p>Legacy configurations using tuples without <c>Domain</c> element, + as well as with all <c>TDomain</c>, <c>Ip</c> and <c>Port</c> elements + still work.</p> </section> <section> diff --git a/lib/snmp/doc/src/snmp_manager_netif.xml b/lib/snmp/doc/src/snmp_manager_netif.xml index 757ed32880..97cedf00c0 100644 --- a/lib/snmp/doc/src/snmp_manager_netif.xml +++ b/lib/snmp/doc/src/snmp_manager_netif.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -50,13 +50,14 @@ <p>The snmp application provides two different modules, <c>snmpm_net_if</c> (the default) and <c>snmpm_net_if_mt</c>, - both uses the UDP as the transport protocol. The difference - between the two modules is that the latter is "multi-threaded", - i.e. for each message/request a new process is created that - process the message/request and then exits. </p> + both uses UDP as the transport protocol i.e the transport domains + <c>transportDomainUdpIpv4</c> and/or <c>transportDomainUdpIpv6</c>. + The difference between the two modules is that the latter is + "multi-threaded", i.e. for each message/request a new process + is created that processes the message/request and then exits. </p> - <p>It is also possible to write your own Net if process, - this section describes how to write a Net if processdo that.</p> + <p>It is also possible to write your own Net if process and + this section describes how to do that.</p> <section> <marker id="mandatory_functions"></marker> @@ -70,11 +71,17 @@ <p>The section <em>Messages</em> describes mandatory messages, which Net if must send to the manager server process. </p> + <p>In this section a <c>Domain</c> field is the transport domain i.e + one of <c>transportDomainUdpIpv4</c> or <c>transportDomainUdpIpv6</c>, + and an <c>Addr</c> field is an + <c>{<seealso marker="kernel:inet#type-ip_address">IpAddr</seealso>, + IpPort}</c> tuple.</p> + <p>Net if must send the following message when it receives an SNMP PDU from the network that is aimed for the MasterAgent: </p> <pre> -Server ! {snmp_pdu, Pdu, Addr, Port} +Server ! {snmp_pdu, Pdu, Domain, Addr} </pre> <list type="bulleted"> <item> @@ -82,14 +89,14 @@ Server ! {snmp_pdu, Pdu, Addr, Port} <c>snmp_types.hrl</c>, with the SNMP request.</p> </item> <item> - <p><c>Addr</c> is the source address. </p> + <p><c>Domain</c> is the source transport domain. </p> </item> <item> - <p><c>Port</c> is port number of the sender.</p> + <p><c>Addr</c> is the source address. </p> </item> </list> <pre> -Server ! {snmp_trap, Trap, Addr, Port} +Server ! {snmp_trap, Trap, Domain, Addr} </pre> <list type="bulleted"> <item> @@ -97,14 +104,14 @@ Server ! {snmp_trap, Trap, Addr, Port} as defined in <c>snmp_types.hrl</c>, with the SNMP request.</p> </item> <item> - <p><c>Addr</c> is the source address. </p> + <p><c>Domain</c> is the source transport domain. </p> </item> <item> - <p><c>Port</c> is port number of the sender.</p> + <p><c>Addr</c> is the source address. </p> </item> </list> <pre> -Server ! {snmp_inform, Ref, Pdu, PduMS, Addr, Port} +Server ! {snmp_inform, Ref, Pdu, PduMS, Domain, Addr} </pre> <list type="bulleted"> <item> @@ -123,14 +130,14 @@ Server ! {snmp_inform, Ref, Pdu, PduMS, Addr, Port} <c>snmp_types.hrl</c>, with the SNMP request.</p> </item> <item> - <p><c>Addr</c> is the source address. </p> + <p><c>Domain</c> is the source transport domain. </p> </item> <item> - <p><c>Port</c> is port number of the sender.</p> + <p><c>Addr</c> is the source address. </p> </item> </list> <pre> -Server ! {snmp_report, Data, Addr, Port} +Server ! {snmp_report, Data, Domain, Addr} </pre> <list type="bulleted"> <item> @@ -152,10 +159,10 @@ Server ! {snmp_report, Data, Addr, Port} <p><c>ReasonInfo</c> is a term().</p> </item> <item> - <p><c>Addr</c> is the source address. </p> + <p><c>Domain</c> is the source transport domain. </p> </item> <item> - <p><c>Port</c> is port number of the sender.</p> + <p><c>Addr</c> is the source address. </p> </item> </list> diff --git a/lib/snmp/doc/src/snmp_target_mib.xml b/lib/snmp/doc/src/snmp_target_mib.xml index be6fa15c73..a076ff2d8e 100644 --- a/lib/snmp/doc/src/snmp_target_mib.xml +++ b/lib/snmp/doc/src/snmp_target_mib.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1998</year><year>2013</year> + <year>1998</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -38,18 +38,18 @@ functions for the SNMP-TARGET-MIB, and functions for configuring the database. </p> <p>The configuration files are described in the SNMP User's Manual.</p> + <p>Legacy API functions <c>add_addr/10</c> that does not specify + transport domain, and <c>add_addr/11</c> that has got separate + <c>IpAddr</c> and <c>PortNumber</c> arguments still work as before + for backwards compatibility reasons.</p> <marker id="types"></marker> </description> <section> <title>DATA TYPES</title> - <code type="none"><![CDATA[ -transportDomain() = transportDomainUdpIpv4 | transportDomainUdpIpv6 -transportAddressIPv4() = [integer()], length 4 -transportAddressIPv6() = [integer()], length 8 -transportAddressMask() = [integer()], length 0 (default), 6 (IPv4) or 10 (IPv6) - ]]></code> + <p>See the <seealso marker="snmpa_conf#types"> + data types in <c>snmpa_conf</c></seealso>.</p> <marker id="configure"></marker> </section> @@ -129,20 +129,18 @@ transportAddressMask() = [integer()], length 0 (default), 6 (IPv4) or 10 (IPv6) </func> <func> - <name>add_addr(Name, Ip, Port, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name> - <name>add_addr(Name, Domain, Ip, Port, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name> + <name>add_addr(Name, Domain, Addr, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name> <fsummary>Add one target address definition</fsummary> <type> <v>Name = string()</v> <v>Domain = transportDomain()</v> - <v>Ip = transportAddressIPv4() | transportAddressIPv6() (depends on the value of Domain)</v> - <v>Port = integer()</v> + <v>Addr = transportAddress() % Default port is 162</v> <v>Timeout = integer()</v> <v>Retry = integer()</v> <v>TagList = string()</v> <v>ParamsName = string()</v> <v>EngineId = string()</v> - <v>TMask = transportAddressMask() (depends on Domain)</v> + <v>TMask = transportAddressMask() % Depends on Domain</v> <v>MMS = integer()</v> <v>Ret = {ok, Key} | {error, Reason}</v> <v>Key = term()</v> diff --git a/lib/snmp/doc/src/snmpa_conf.xml b/lib/snmp/doc/src/snmpa_conf.xml index 99a56cd601..2780cec156 100644 --- a/lib/snmp/doc/src/snmpa_conf.xml +++ b/lib/snmp/doc/src/snmpa_conf.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2006</year><year>2013</year> + <year>2006</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -45,20 +45,56 @@ <title>DATA TYPES</title> <code type="none"><![CDATA[ transportDomain() = transportDomainUdpIpv4 | transportDomainUdpIpv6 -transportAddressIPv4() = [integer()], length 4 -transportAddressIPv6() = [integer()], length 8 -transportAddressMask() = [integer()], length 0 (default), 6 (IPv4) or 10 (IPv6) + +transportAddress() = + transportAddressIPv4() | transportAddressIPv6() + +transportAddressWithPort() = + transportAddressIPv4WithPort() | transportAddressIPv6WithPort() + +transportAddressWithoutPort() = + transportAddressIPv4WithoutPort() | transportAddressIPv6WithoutPort() + +transportAddressIPv4() = + transportAddressIPv4WithPort() | transportAddressIPv4WithoutPort() +transportAddressIPv4WithPort = + {transportAddressIPv4WithoutPort(), inet:port_number()} | + [byte() x 4, byte() x 2] +transportAddressIPv4WithoutPort = + inet:ip4_address() | [byte() x 4] + +transportAddressIPv6() = + transportAddressIPv6WithPort() | transportAddressIPv6WithoutPort() +transportAddressIPv6WithPort = + {transportAddressIPv6WithoutPort(), inet:port_number()} | + [word() x 8, inet:port_number()] | + [word() x 8, byte() x 2] | + {byte() x 16, byte() x 2] +transportAddressIPv6WithoutPort = + inet:ip6_address() | [word() x 8] | [byte() x 16] + +transportAddressMask() = + [] | transportAddressWithPort() + +byte() = 0..255 +word() = 0..65535 ]]></code> + <p>For <c>inet:ip4_address()</c>, <c>inet:ip6_address()</c> + and <c>inet:port_number()</c>, see also + <seealso marker="kernel:inet#type-ip_address"> + <c>inet:ip_address()</c></seealso></p> <marker id="agent_entry"></marker> </section> + + <funcs> <func> <name>agent_entry(Tag, Val) -> agent_entry()</name> <fsummary>Create an agent entry</fsummary> <type> - <v>Tag = intAgentIpAddress | intAgentUDPPort | intAgentMaxPacketSize | snmpEngineMaxMessageSize | snmpEngineID</v> + <v>Tag = intAgentTransports | intAgentUDPPort | intAgentMaxPacketSize | snmpEngineMaxMessageSize | snmpEngineID</v> <v>Val = term()</v> <v>agent_entry() = term()</v> </type> @@ -390,17 +426,15 @@ transportAddressMask() = [integer()], length 0 (default), 6 (IPv4) or 10 (IPv6) </func> <func> - <name>target_addr_entry(Name, Ip, TagList, ParamsName, EngineId) -> target_addr_entry()</name> - <name>target_addr_entry(Name, Ip, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name> - <name>target_addr_entry(Name, Ip, Udp, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> - <name>target_addr_entry(Name, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> - <name>target_addr_entry(Name, Domain, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> + <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId) -> target_addr_entry()</name> + <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name> + <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> + <name>target_addr_entry(Name, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> <fsummary>Create an target_addr entry</fsummary> <type> <v>Name = string()</v> <v>Domain = transportDomain()</v> - <v>Ip = transportAddressIPv4() | transportAddressIPv6() (depends on Domain)</v> - <v>Udp = integer()</v> + <v>Ip = transportAddress() (depends on Domain)</v> <v>Timeout = integer()</v> <v>RetryCount = integer()</v> <v>TagList = string()</v> @@ -414,12 +448,12 @@ transportAddressMask() = [integer()], length 0 (default), 6 (IPv4) or 10 (IPv6) <p>Create an entry for the agent target_addr config file, <c>target_addr.conf</c>. </p> <p><c>Name</c> must be a <em>non-empty</em> string. </p> - <p><c>target_addr_entry/5</c> translates to the following call: - <c>target_addr_entry(Name, Ip, TagList, ParamsName, EngineId)</c>. </p> <p><c>target_addr_entry/6</c> translates to the following call: - <c>target_addr_entry(Name, Ip, 162, TagList, ParamsName, EngineId, TMask, 2048)</c>. </p> + <c>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, [])</c>. </p> + <p><c>target_addr_entry/7</c> translates to the following call: + <c>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, 2048)</c>. </p> <p><c>target_addr_entry/8</c> translates to the following call: - <c>target_addr_entry(Name, Ip, Udp, 1500, 3, TagList, ParamsName, EngineId, TMask, MaxMessageSize)</c>. </p> + <c>target_addr_entry(Name, Domain, Addr, 1500, 3, TagList, ParamsName, EngineId, TMask, MaxMessageSize)</c>. </p> <p>See <seealso marker="snmp_agent_config_files#target_addr">Target Address Definitions</seealso> for more info. </p> diff --git a/lib/snmp/doc/src/snmpa_mpd.xml b/lib/snmp/doc/src/snmpa_mpd.xml index c5ab0a0520..518100d30c 100644 --- a/lib/snmp/doc/src/snmpa_mpd.xml +++ b/lib/snmp/doc/src/snmpa_mpd.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1999</year><year>2013</year> + <year>1999</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -43,6 +43,12 @@ <marker id="init"></marker> </description> + <section> + <title>DATA TYPES</title> + <p>See the <seealso marker="snmpa_conf#types"> + data types in <c>snmpa_conf</c></seealso>.</p> + </section> + <funcs> <func> <name>init(Vsns) -> mpd_state()</name> @@ -63,16 +69,17 @@ </func> <func> - <name>process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> - <name>process_packet(Packet, TDomain, TAddress, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> + <name>process_packet(Packet, From, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> + <name>process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> <fsummary>Process a packet received from the network</fsummary> <type> <v>Packet = binary()</v> - <v>TDomain = snmpUDPDomain</v> - <v>TAddress = {Ip, Udp}</v> + <v>From = {TDomain, TAddr}</v> + <v>TDomain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v> + <v>TAddr = {IpAddr, IpPort}</v> <v>LocalEngineID = string()</v> - <v>Ip = {integer(), integer(), integer(), integer()}</v> - <v>Udp = integer()</v> + <v>IpAddr = <seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso></v> + <v>IpPort = inet:port_number()</v> <v>State = mpd_state()</v> <v>NoteStore = pid()</v> <v>Log = snmp_log()</v> @@ -85,7 +92,7 @@ </type> <desc> <p>Processes an incoming packet. Performs authentication and - decryption as necessary. The return values should be passed the + decryption as necessary. The return values should be passed to the agent.</p> <note> @@ -150,14 +157,20 @@ network. </p> <p><c>MsgData</c> is the message specific data used in - the SNMP message. This value is received in a <c>send_pdu</c> - or <c>send_pdu_req</c> message from the agent. In SNMPv1 and + the SNMP message. This value is received in a + <seealso marker="snmp_agent_netif#im_send_pdu"><c>send_pdu</c></seealso> + or + <seealso marker="snmp_agent_netif#im_send_pdu_req"> + <c>send_pdu_req</c></seealso> + message from the agent. In SNMPv1 and SNMPv2c, this message data is the community string. In - SNMPv3, it is the context information. - <c>To</c> is a list of the destination addresses and + SNMPv3, it is the context information.</p> + <p> + <c>To</c> is a list of destination addresses and their corresponding security parameters. This value is - also received from the requests mentioned above. - </p> + received in the same message from the agent and then transformed + trough <seealso marker="#process_taddrs"><c>process_taddrs</c></seealso> + before passed to this function.</p> <note> <p>Note that the use of the LocalEngineID argument is only intended @@ -166,6 +179,32 @@ (see SNMP-FRAMEWORK-MIB). </p> </note> + <marker id="process_taddrs"></marker> + </desc> + </func> + + <func> + <name>process_taddrs(TDests) -> Dests</name> + <fsummary>Transform addresses from internal MIB format to a less internal + </fsummary> + <type> + <v>TDests = [TDest]</v> + <v>TDest = {{TDomain, TAddr}, SecData} | {TDomain, TAddr}</v> + <v>TDomain = term() % Not at tuple</v> + <v>TAddr = term()</v> + <v>SecData = term()</v> + <v>Dests = [Dest]</v> + <v>Dest = {{Domain, Addr}, SecData} | {Domain, Addr}</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddress() % Depends on Domain</v> + </type> + <desc> + <p>Transforms addresses from internal MIB format to one + more useful to <seealso marker="snmp_agent_netif">Agent Net if</seealso>. + </p> + <p>See also <seealso marker="#generate_msg"><c>generate_msg</c>.</seealso> + </p> + <marker id="discarded_pdu"></marker> </desc> </func> diff --git a/lib/snmp/doc/src/snmpa_network_interface_filter.xml b/lib/snmp/doc/src/snmpa_network_interface_filter.xml index e08a26ed92..eb640e1bc3 100644 --- a/lib/snmp/doc/src/snmpa_network_interface_filter.xml +++ b/lib/snmp/doc/src/snmpa_network_interface_filter.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2007</year><year>2013</year> + <year>2007</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -58,10 +58,10 @@ on two levels: </p> <list type="bulleted"> <item> - <p>The first level is at the UDP entry / exit point, i.e. - immediately after the receipt of the message, before any message + <p>The first level is at the transport entry / exit point, i.e. + immediately after the receipt of the message before any message processing is done (accept_recv) and - immediately before sending the message, after all message + immediately before sending the message after all message processing is done (accept_send).</p> </item> <item> @@ -78,6 +78,12 @@ <seealso marker="snmp_app#configuration_params">req_limit</seealso> and the function <seealso marker="snmpa#register_notification_filter">register_notification_filter</seealso>. </p> + <p>Legacy network interface filter modules used arguments on the form + <c>(IpAddr, PortNumber,...)</c> instead of + <c>(Domain, Addr, ...)</c>, and if the SNMP agent is run without + changing the configuration to use transport domains + the network interface filter will still get + the old arguments and work as before.</p> </description> <section> @@ -88,15 +94,17 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | 'set-request' | trap | 'get-bulk-request' | 'inform-request' | report </code> + <p>See also the <seealso marker="snmpa_conf#types"> + data types in <c>snmpa_conf</c></seealso>.</p> <marker id="accept_recv"></marker> </section> <funcs> <func> - <name>accept_recv(Ip, Port) -> boolean()</name> + <name>accept_recv(Domain, Addr) -> boolean()</name> <fsummary>Shall the received message be accepted</fsummary> <type> - <v>Ip = ip_address()</v> - <v>Port = port()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> </type> <desc> <p>Called at the reception of a message (before <em>any</em> processing @@ -107,11 +115,11 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </desc> </func> <func> - <name>accept_send(Ip, Port) -> boolean()</name> + <name>accept_send(Domain, Addr) -> boolean()</name> <fsummary>Shall the message be sent</fsummary> <type> - <v>Ip = ip_address()</v> - <v>Port = port()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> </type> <desc> <p>Called before the sending of a message (after <em>all</em> processing @@ -122,11 +130,11 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </desc> </func> <func> - <name>accept_recv_pdu(Ip, Port, PduType) -> boolean()</name> + <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name> <fsummary>Shall the received pdu be accepted</fsummary> <type> - <v>Ip = ip_address()</v> - <v>Port = port()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> <v>PduType = pdu_type()</v> </type> <desc> @@ -144,7 +152,9 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | <type> <v>Targets = targets()</v> <v>targets() = [target()]</v> - <v>target() = {ip_address(), port()}</v> + <v>target() = {Domain, Addr}</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> <v>PduType = pdu_type() > 0</v> <v>Reply = boolean() | NewTargets</v> <v>NewTargets = targets()</v> @@ -155,7 +165,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | <p>For the message to be discarded all together, the function <em>must</em> return <em>false</em>. </p> <p>Note that it is possible for this function to filter out targets - (but <em>not</em> add its own) by returning an updated + (but <em>not</em> to add its own) by returning an updated <c>Targets</c> list (<c>NewTargets</c>). </p> </desc> </func> diff --git a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml index aff71688b6..814f02a14c 100644 --- a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml +++ b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2013</year> + <year>2014</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -45,22 +45,30 @@ must export the following functions: </p> <list type="bulleted"> <item> - <p><seealso marker="#delivery_targets">delivery_targets/3</seealso></p> + <p><seealso marker="#delivery_targets/3">delivery_targets/3</seealso></p> </item> <item> - <p><seealso marker="#delivery_info">delivery_info/4</seealso></p> + <p><seealso marker="#delivery_info/4">delivery_info/4</seealso></p> </item> </list> <p>The semantics of them and their exact signatures are explained below. </p> + <p>Legacy notification delivery information receiver modules + used a target argument on the form + <c>{IpAddr, PortNumber}</c> instead of + <c>{Domain, Addr}</c>, and if the SNMP Agent is run without + changing the configuration to use transport domains + the notification delivery information receiver will still get + the old arguments and work as before.</p> + </description> <section> <title>DATA TYPES</title> - <code type="none"><![CDATA[ -address() = A 4-tuple - ]]></code> + <p>See the <seealso marker="snmpa_conf#types"> + data types in <c>snmpa_conf</c></seealso>.</p> + <marker id="accept_recv"></marker> <marker id="delivery_targets"></marker> </section> @@ -71,10 +79,8 @@ address() = A 4-tuple <fsummary>Inform about target addresses</fsummary> <type> <v>Tag = term()</v> - <v>Targets = [target()]</v> - <v>target() = {Address, Port}</v> - <v>Address = address()</v> - <v>Port = integer()</v> + <v>Targets = [Target]</v> + <v>Target = {transportDomain(), transportAddressWithPort()</v> <v>Extra = term()</v> </type> <desc> @@ -94,10 +100,8 @@ address() = A 4-tuple <fsummary>Inform about delivery result</fsummary> <type> <v>Tag = term()</v> - <v>Target = target()</v> - <v>target() = {Address, Port}</v> - <v>Address = address()</v> - <v>Port = integer()</v> + <v>Targets = [Target]</v> + <v>Target = {transportDomain(), transportAddressWithPort()</v> <v>DeliveryResult = delivery_result()</v> <v>delivery_result() = no_response | got_response</v> <v>Extra = term()</v> diff --git a/lib/snmp/doc/src/snmpm.xml b/lib/snmp/doc/src/snmpm.xml index dc8226bb87..ff90e49968 100644 --- a/lib/snmp/doc/src/snmpm.xml +++ b/lib/snmp/doc/src/snmpm.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -69,6 +69,9 @@ sec_name() = string() sec_level() = noAuthNoPriv | authNoPriv | authPriv ]]></code> + <p>See also the <seealso marker="snmpa_conf#types"> + data types in <c>snmpa_conf</c></seealso>.</p> + <marker id="monitor"></marker> </section> <funcs> @@ -300,9 +303,9 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv <p>The type of <c>Val</c> depends on <c>Item</c>: </p> <code type="none"><![CDATA[ [mandatory] engine_id = string() -[mandatory] address = ip_address() -[optional] port = integer() -[optional] tdomain = transportDomainUdpIpv4 | transportDomainUdpIpv6 +[mandatory] tadress = transportAddress() % Depends on tdomain +[optional] port = inet:port_number() +[optional] tdomain = transportDomain() [optional] community = string() [optional] timeout = integer() | snmp_timer() [optional] max_message_size = integer() @@ -313,7 +316,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv ]]></code> <p>Note that if no <c>tdomain</c> is given, the default value, <c>transportDomainUdpIpv4</c>, is used.</p> - <p>Note that if no <c>port</c> is given, the default value is used.</p> + <p>Note that if no <c>port</c> is given and if <c>taddress</c> does not + contain a port number, the default value is used.</p> <marker id="unregister_agent"></marker> </desc> diff --git a/lib/snmp/doc/src/snmpm_conf.xml b/lib/snmp/doc/src/snmpm_conf.xml index 0cc9ff3379..8635fb705b 100644 --- a/lib/snmp/doc/src/snmpm_conf.xml +++ b/lib/snmp/doc/src/snmpm_conf.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2006</year><year>2013</year> + <year>2006</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -195,14 +195,14 @@ </desc> </func> <func> - <name>agents_entry(UserId, TargetName, Comm, Ip, Port, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name> + <name>agents_entry(UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name> <fsummary>Create an agents entry</fsummary> <type> <v>UserId = term()</v> <v>TargetName = string()</v> <v>Comm = string()</v> - <v>Ip = string()</v> - <v>Port = integer()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddress()</v> <v>EngineID = string()</v> <v>Timeout = integer()</v> <v>MaxMessageSize = integer()</v> diff --git a/lib/snmp/doc/src/snmpm_mpd.xml b/lib/snmp/doc/src/snmpm_mpd.xml index ad72fd7bc0..c23b2b6833 100644 --- a/lib/snmp/doc/src/snmpm_mpd.xml +++ b/lib/snmp/doc/src/snmpm_mpd.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -39,7 +39,12 @@ It is supposed to be used from a Network Interface process (<seealso marker="snmp_manager_netif">Definition of Manager Net if</seealso>). </p> + + <p>Legacy API function <c>process_msg/7</c> that has got separate + <c>IpAddr</c> and <c>PortNumber</c> arguments still works as before + for backwards compatibility reasons.</p> </description> + <funcs> <func> <name>init_mpd(Vsns) -> mpd_state()</name> @@ -58,13 +63,12 @@ </desc> </func> <func> - <name>process_msg(Msg, TDomain, Addr, Port, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name> + <name>process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name> <fsummary>Process a message received from the network</fsummary> <type> <v>Msg = binary()</v> - <v>TDomain = snmpUDPDomain</v> - <v>Addr = {integer(), integer(), integer(), integer()}</v> - <v>Port = integer()</v> + <v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v> + <v>Addr = {<seealso marker="kernel:inet#type-ip_address">inet:ip_address(), inet:port_number()</seealso>} </v> <v>State = mpd_state()</v> <v>NoteStore = pid()</v> <v>Logger = function()</v> diff --git a/lib/snmp/doc/src/snmpm_network_interface.xml b/lib/snmp/doc/src/snmpm_network_interface.xml index 6cf7bd6ed7..bea6b46dc7 100644 --- a/lib/snmp/doc/src/snmpm_network_interface.xml +++ b/lib/snmp/doc/src/snmpm_network_interface.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -69,6 +69,10 @@ <p>The semantics of them and their exact signatures are explained below. </p> + <p>Legacy API function <c>send_pdu/7</c> that has got separate + <c>IpAddr</c> and <c>PortNumber</c> arguments still works as before + for backwards compatibility reasons.</p> + <marker id="start_link"></marker> </description> @@ -103,15 +107,15 @@ </func> <func> - <name>send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo) -> void()</name> + <name>send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> void()</name> <fsummary>Request the network interface process to send this pdu</fsummary> <type> <v>Pid = pid()</v> <v>Pdu = pdu()</v> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> <v>MsgData = term()</v> - <v>Addr = address()</v> - <v>Port = integer()</v> + <v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v> + <v>Addr = {<seealso marker="kernel:inet#type-ip_address">inet:ip_address(), inet:port_number()</seealso>} </v> <v>ExtraInfo = term()</v> </type> <desc> diff --git a/lib/snmp/doc/src/snmpm_network_interface_filter.xml b/lib/snmp/doc/src/snmpm_network_interface_filter.xml index f0526269b3..1ef4f29c0f 100644 --- a/lib/snmp/doc/src/snmpm_network_interface_filter.xml +++ b/lib/snmp/doc/src/snmpm_network_interface_filter.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2007</year><year>2013</year> + <year>2007</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -76,6 +76,12 @@ The default filter accepts all messages.</p> <p>A network interface filter can e.g. be used during testing or for load regulation. </p> + <p>Legacy network interface filter modules used arguments on the form + <c>(IpAddr, PortNumber,...)</c> instead of + <c>(Domain, Addr, ...)</c>, and if the SNMP manager is run without + changing the configuration to use transport domains + the network interface filter will still get + the old arguments and work as before.</p> </description> <section> @@ -86,16 +92,18 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | 'set-request' | trap | 'get-bulk-request' | 'inform-request' | report | trappdu </code> + <p>See also the <seealso marker="snmpa_conf#types"> + data types in <c>snmpa_conf</c></seealso>.</p> <marker id="accept_recv"></marker> </section> <funcs> <func> - <name>accept_recv(Addr, Port) -> boolean()</name> + <name>accept_recv(Domain, Addr) -> boolean()</name> <fsummary>Shall the received message be accepted</fsummary> <type> - <v>Addr = ip_address()</v> - <v>Port = port()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> </type> <desc> <p>Called at the reception of a message (before <em>any</em> processing @@ -107,11 +115,11 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </func> <func> - <name>accept_send(Addr, Port) -> boolean()</name> + <name>accept_send(Domain, Addr) -> boolean()</name> <fsummary>Shall the message be sent</fsummary> <type> - <v>Addr = ip_address()</v> - <v>Port = port()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> </type> <desc> <p>Called before the sending of a message (after <em>all</em> processing @@ -123,11 +131,11 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </func> <func> - <name>accept_recv_pdu(Addr, Port, PduType) -> boolean()</name> + <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name> <fsummary>Shall the received pdu be accepted</fsummary> <type> - <v>Addr = ip_address()</v> - <v>Port = port()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> <v>PduType = pdu_type()</v> </type> <desc> @@ -141,11 +149,11 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </func> <func> - <name>accept_send_pdu(Addr, Port, PduType) -> boolean()</name> + <name>accept_send_pdu(Domain, Addr, PduType) -> boolean()</name> <fsummary>Shall the pdu be sent</fsummary> <type> - <v>Addr = ip_address()</v> - <v>Port = port()</v> + <v>Domain = transportDomain()</v> + <v>Addr = transportAddressWithPort()</v> <v>PduType = pdu_type() > 0</v> </type> <desc> diff --git a/lib/snmp/doc/src/snmpm_user.xml b/lib/snmp/doc/src/snmpm_user.xml index 6f412d90f8..a4492839cd 100644 --- a/lib/snmp/doc/src/snmpm_user.xml +++ b/lib/snmp/doc/src/snmpm_user.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -63,10 +63,15 @@ <p>The semantics of them and their exact signatures are explained below. </p> <p>Some of the function has no defined return value (<c>void()</c>), - they can ofcourse return anythyng. But the functions that do have + they can of course return anything. But the functions that do have specified return value(s) <em>must</em> adhere to this. None of the functions can use exit of throw to return. </p> + <p>If the manager is not configured to use any particular + transport domain, the behaviour <c>handle_agent/4</c> + will for backwards copmpatibility reasons be called with the old + <c>IpAddr</c> and <c>PortNumber</c> arguments</p> + <marker id="types"></marker> </description> @@ -116,11 +121,11 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), </func> <func> - <name>handle_agent(Addr, Port, Type, SnmpInfo, UserData) -> Reply</name> + <name>handle_agent(Domain, Addr, Type, SnmpInfo, UserData) -> Reply</name> <fsummary>Handle agent</fsummary> <type> - <v>Addr = ip_address()</v> - <v>Port = integer()</v> + <v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v> + <v>Addr = {<seealso marker="kernel:inet#type-ip_address">inet:ip_address(), inet:port_number()</seealso>} </v> <v>Type = pdu | trap | report | inform</v> <v>SnmpInfo = SnmpPduInfo | SnmpTrapInfo | SnmpReportInfo | SnmpInformInfo</v> <v>SnmpPduInfo = snmp_gen_info()</v> diff --git a/lib/snmp/src/agent/snmp_framework_mib.erl b/lib/snmp/src/agent/snmp_framework_mib.erl index 9d3f7ef5e7..6ff9224d34 100644 --- a/lib/snmp/src/agent/snmp_framework_mib.erl +++ b/lib/snmp/src/agent/snmp_framework_mib.erl @@ -207,22 +207,28 @@ check_agent({intAgentIpAddress = Tag, Ip} = Entry, {Domain, Port} = State) -> [{Tag, FixedIp}, {intAgentTransports, [{Domain, {FixedIp, Port}}]}] end, State}; -check_agent({intAgentTransports = Tag, Transports}, {_, Port} = State) -> +check_agent({intAgentTransports = Tag, Transports}, {_, Port} = State) + when is_list(Transports) -> CheckedTransports = - [case - case Port of - undefined -> - snmp_conf:check_address(Domain, Address); - _ -> - snmp_conf:check_address(Domain, Address, Port) - end - of - ok -> - Transport; - {ok, FixedAddress} -> - {Domain, FixedAddress} + [case Transport of + {Domain, Address} -> + case + case Port of + undefined -> + snmp_conf:check_address(Domain, Address); + _ -> + snmp_conf:check_address(Domain, Address, Port) + end + of + ok -> + Transport; + {ok, FixedAddress} -> + {Domain, FixedAddress} + end; + _ -> + error({bad_transport, Transport}) end - || {Domain, Address} = Transport <- Transports], + || Transport <- Transports], {{ok, {Tag, CheckedTransports}}, State}; check_agent(Entry, State) -> {check_agent(Entry), State}. diff --git a/lib/snmp/src/agent/snmp_target_mib.erl b/lib/snmp/src/agent/snmp_target_mib.erl index e916f17d6a..ef9503cda8 100644 --- a/lib/snmp/src/agent/snmp_target_mib.erl +++ b/lib/snmp/src/agent/snmp_target_mib.erl @@ -182,6 +182,10 @@ check_target_addr( Name, Domain, Address, Timeout, RetryCount, TagList, Params, EngineId, TMask, MMS); check_target_addr( + {_Name, Domain, Address, _Timeout, _RetryCount, _TagList, _Params, + _EngineId, _TMask, _MMS}) -> % Arity 10 + error({bad_address, {Domain, Address}}); +check_target_addr( {Name, Domain, Address, Timeout, RetryCount, TagList, Params, EngineId}) % Arity 8 when is_atom(Domain) -> @@ -197,6 +201,10 @@ check_target_addr( check_target_addr( Name, Domain, Address, Timeout, RetryCount, TagList, Params, EngineId); +check_target_addr( + {_Name, Domain, Address, _Timeout, _RetryCount, _TagList, _Params, + _EngineId}) ->% Arity 8 + error({bad_address, {Domain, Address}}); %% Use dummy engine id if the old style is found check_target_addr( {Name, Domain, Address, Timeout, RetryCount, TagList, Params}) % Arity 7 @@ -210,6 +218,9 @@ check_target_addr( Address = {Ip, Udp}, check_target_addr( Name, Domain, Address, Timeout, RetryCount, TagList, Params); +check_target_addr( + {_Name, Domain, Address, _Timeout, _RetryCount, _TagList, _Params}) -> % Arity 7 + error({bad_address, {Domain, Address}}); %% Use dummy engine id if the old style is found check_target_addr( {Name, Domain, Address, Timeout, RetryCount, TagList, Params, @@ -225,6 +236,10 @@ check_target_addr( Address = {Ip, Udp}, check_target_addr( Name, Domain, Address, Timeout, RetryCount, TagList, Params, TMask, MMS); +check_target_addr( + {_Name, Domain, Address, _Timeout, _RetryCount, _TagList, _Params, + _TMask, _MMS}) -> % Arity 9 + error({bad_address, {Domain, Address}}); check_target_addr(X) -> error({invalid_target_addr, X}). @@ -291,7 +306,7 @@ check_engine_id(EngineId) -> snmp_conf:check_string(EngineId). check_address(Domain, Address) -> - case snmp_conf:check_address(Domain, Address) of + case snmp_conf:check_address(Domain, Address, 162) of ok -> Address; {ok, NAddress} -> @@ -301,7 +316,11 @@ check_address(Domain, Address) -> check_mask(_Domain, [] = Mask) -> Mask; check_mask(Domain, Mask) -> - try check_address(Domain, Mask) + try snmp_conf:check_address(Domain, Mask) of + ok -> + Mask; + {ok, NMask} -> + NMask catch {error, {bad_address, Info}} -> error({bad_mask, Info}) @@ -365,16 +384,21 @@ table_del_row(Tab, Key) -> snmpa_mib_lib:table_del_row(db(Tab), Key). -add_addr(Name, Ip, Port, Timeout, Retry, TagList, - Params, EngineId, TMask, MMS) -> - Domain = default_domain(), - add_addr(Name, Domain, Ip, Port, Timeout, Retry, TagList, - Params, EngineId, TMask, MMS). - -add_addr(Name, Domain, Ip, Port, Timeout, Retry, TagList, - Params, EngineId, TMask, MMS) -> - Addr = {Name, Domain, Ip, Port, Timeout, Retry, TagList, - Params, EngineId, TMask, MMS}, +add_addr( + Name, Domain_or_Ip, Addr_or_Port, Timeout, Retry, TagList, Params, + EngineId, TMask, MMS) -> + add_addr( + {Name, Domain_or_Ip, Addr_or_Port, Timeout, Retry, TagList, Params, + EngineId, TMask, MMS}). +%% +add_addr( + Name, Domain, Ip, Port, Timeout, Retry, TagList, Params, + EngineId, TMask, MMS) -> + add_addr( + {Name, Domain, Ip, Port, Timeout, Retry, TagList, Params, + EngineId, TMask, MMS}). +%% +add_addr(Addr) -> case (catch check_target_addr(Addr)) of {ok, Row} -> Key = element(1, Row), diff --git a/lib/snmp/src/agent/snmpa_conf.erl b/lib/snmp/src/agent/snmpa_conf.erl index b4d32dc928..534d0e447b 100644 --- a/lib/snmp/src/agent/snmpa_conf.erl +++ b/lib/snmp/src/agent/snmpa_conf.erl @@ -47,7 +47,7 @@ read_standard_config/1, %% target_addr.conf - target_addr_entry/5, target_addr_entry/6, + target_addr_entry/5, target_addr_entry/6, target_addr_entry/7, target_addr_entry/8, target_addr_entry/10, target_addr_entry/11, write_target_addr_config/2, write_target_addr_config/3, append_target_addr_config/2, @@ -386,6 +386,12 @@ target_addr_entry( target_addr_entry(Name, Ip, TagList, ParamsName, EngineId, []). target_addr_entry( + Name, Domain, Addr, TagList, + ParamsName, EngineId) when is_atom(Domain) -> + target_addr_entry( + Name, Domain, Addr, TagList, + ParamsName, EngineId, []); +target_addr_entry( Name, Ip, TagList, ParamsName, EngineId, TMask) -> target_addr_entry( @@ -394,6 +400,13 @@ target_addr_entry( target_addr_entry( Name, Domain_or_Ip, Addr_or_Port, TagList, + ParamsName, EngineId, TMask) -> + target_addr_entry( + Name, Domain_or_Ip, Addr_or_Port, TagList, + ParamsName, EngineId, TMask, 2048). + +target_addr_entry( + Name, Domain_or_Ip, Addr_or_Port, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry( Name, Domain_or_Ip, Addr_or_Port, 1500, 3, TagList, @@ -475,6 +488,16 @@ write_target_addr_conf(Fd, Conf) -> do_write_target_addr_conf( Fd, + {Name, Domain, Address, Timeout, RetryCount, TagList, + ParamsName, EngineId, TMask, MaxMessageSize}) + when is_atom(Domain) -> + io:format( + Fd, + "{\"~s\", ~w, ~w, ~w, ~w, \"~s\", \"~s\", \"~s\", ~w, ~w}.~n", + [Name, Domain, Address, Timeout, RetryCount, TagList, + ParamsName, EngineId, TMask, MaxMessageSize]); +do_write_target_addr_conf( + Fd, {Name, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize}) when is_integer(Udp) -> @@ -485,15 +508,10 @@ do_write_target_addr_conf( {Name, Domain, Address, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize}); do_write_target_addr_conf( - Fd, - {Name, Domain, Address, Timeout, RetryCount, TagList, - ParamsName, EngineId, TMask, MaxMessageSize}) - when is_atom(Domain) -> - io:format( - Fd, - "{\"~s\", ~w, ~w, ~w, ~w, \"~s\", \"~s\", \"~s\", ~w, ~w}.~n", - [Name, Domain, Address, Timeout, RetryCount, TagList, - ParamsName, EngineId, TMask, MaxMessageSize]); + _Fd, + {_Name, Domain, Address, _Timeout, _RetryCount, _TagList, + _ParamsName, _EngineId, _TMask, _MaxMessageSize}) -> + error({bad_address, {Domain, Address}}); do_write_target_addr_conf( Fd, {Name, Domain, Ip, Udp, Timeout, RetryCount, TagList, diff --git a/lib/snmp/src/app/snmp.appup.src b/lib/snmp/src/app/snmp.appup.src index ae79e3c1d1..1cc1a17b1d 100644 --- a/lib/snmp/src/app/snmp.appup.src +++ b/lib/snmp/src/app/snmp.appup.src @@ -28,6 +28,7 @@ %% {update, snmpa_local_db, soft, soft_purge, soft_purge, []} %% {add_module, snmpm_net_if_mt} [ + {"5.0", [{restart_application, snmp}]}, {"4.25.1", [{restart_application, snmp}]}, {"4.25.0.1", [{restart_application, snmp}]}, {"4.25", [{restart_application, snmp}]}, @@ -42,6 +43,7 @@ %% {remove, {snmpm_net_if_mt, soft_purge, soft_purge}} [ + {"5.0", [{restart_application, snmp}]}, {"4.25.1", [{restart_application, snmp}]}, {"4.25.0.1", [{restart_application, snmp}]}, {"4.25", [{restart_application, snmp}]}, diff --git a/lib/snmp/src/manager/depend.mk b/lib/snmp/src/manager/depend.mk index 2e7783c8ed..60f61b0d3b 100644 --- a/lib/snmp/src/manager/depend.mk +++ b/lib/snmp/src/manager/depend.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2012. All Rights Reserved. +# Copyright Ericsson AB 2004-2014. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -50,10 +50,11 @@ $(EBIN)/snmpm_net_if.$(EMULATOR): \ snmpm_network_interface.erl $(EBIN)/snmpm_net_if_mt.$(EMULATOR): \ + snmpm_net_if_mt.erl \ ../../include/snmp_types.hrl \ ../misc/snmp_debug.hrl \ ../misc/snmp_verbosity.hrl \ - snmpm_net_if_mt.erl \ + snmpm_net_if.erl \ snmpm_network_interface.erl $(EBIN)/snmpm_server.$(EMULATOR): \ diff --git a/lib/snmp/src/manager/snmpm_conf.erl b/lib/snmp/src/manager/snmpm_conf.erl index 888f19aec6..087ef6c6ea 100644 --- a/lib/snmp/src/manager/snmpm_conf.erl +++ b/lib/snmp/src/manager/snmpm_conf.erl @@ -114,6 +114,7 @@ do_write_manager_conf(Fd, {Tag, Val}) when Tag =:= domain; Tag =:= address; Tag =:= port; + Tag =:= transports; Tag =:= max_message_size -> io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); do_write_manager_conf(Fd, {Tag, Val}) @@ -199,9 +200,10 @@ do_write_users_conf(_Fd, Crap) -> %% ------ agents.conf ------ %% -agents_entry(UserId, TargetName, Comm, Ip, Port, EngineID, Timeout, - MaxMessageSize, Version, SecModel, SecName, SecLevel) -> - {UserId, TargetName, Comm, Ip, Port, EngineID, Timeout, +agents_entry( + UserId, TargetName, Comm, Domain_or_Ip, Addr_or_Port, EngineID, Timeout, + MaxMessageSize, Version, SecModel, SecName, SecLevel) -> + {UserId, TargetName, Comm, Domain_or_Ip, Addr_or_Port, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel}. diff --git a/lib/snmp/src/manager/snmpm_config.erl b/lib/snmp/src/manager/snmpm_config.erl index 013fefa4e2..5cab81baf6 100644 --- a/lib/snmp/src/manager/snmpm_config.erl +++ b/lib/snmp/src/manager/snmpm_config.erl @@ -442,8 +442,31 @@ agent_info(TargetName, all) -> All -> {ok, [{Item, Val} || {{_, Item}, Val} <- All]} end; +%% Begin backwards compatibility +agent_info(TargetName, address) -> + case agent_info({TargetName, taddress}) of + {ok, Val} -> + {Addr, _} = Val, + {ok, Addr}; + _ -> + %% This should be redundant since 'taddress' should exist + agent_info({TargetName, address}) + end; +agent_info(TargetName, port) -> + case agent_info({TargetName, taddress}) of + {ok, Val} -> + {_, Port} = Val, + {ok, Port}; + _ -> + %% This should be redundant since 'taddress' should exist + agent_info({TargetName, port}) + end; +%% End backwards compatibility agent_info(TargetName, Item) -> - case ets:lookup(snmpm_agent_table, {TargetName, Item}) of + agent_info({TargetName, Item}). + +agent_info(Key) -> + case ets:lookup(snmpm_agent_table, Key) of [{_, Val}] -> {ok, Val}; [] -> @@ -456,29 +479,29 @@ agent_info(Domain, Address, Item) when is_atom(Domain) -> do_agent_info(Domain, NAddress, Item) catch _Thrown -> - p(?MODULE_STRING":agent_info(~p, ~p, ~p) throwed ~p at.~n" - " ~p", - [Domain, Address, Item, _Thrown, erlang:get_stacktrace()]), + %% p(?MODULE_STRING":agent_info(~p, ~p, ~p) throwed ~p at.~n" + %% " ~p", + %% [Domain, Address, Item, _Thrown, erlang:get_stacktrace()]), {error, not_found} end; -agent_info(Ip, Port, Item) -> - p(?MODULE_STRING":agent_info(~p, ~p, ~p) entry~n", - [Ip, Port, Item]), +agent_info(Ip, Port, Item) when is_integer(Port) -> + %% p(?MODULE_STRING":agent_info(~p, ~p, ~p) entry~n", + %% [Ip, Port, Item]), Domain = default_transport_domain(), try fix_address(Domain, {Ip, Port}) of Address -> do_agent_info(Domain, Address, Item) catch _Thrown -> - p(?MODULE_STRING":agent_info(~p, ~p, ~p) throwed ~p at.~n" - " ~p", - [Ip, Port, Item, _Thrown, erlang:get_stacktrace()]), + %% p(?MODULE_STRING":agent_info(~p, ~p, ~p) throwed ~p at.~n" + %% " ~p", + %% [Ip, Port, Item, _Thrown, erlang:get_stacktrace()]), {error, not_found} end. do_agent_info(Domain, Address, target_name = Item) -> - p(?MODULE_STRING":do_agent_info(~p, ~p, ~p) entry~n", - [Domain, Address, Item]), + %% p(?MODULE_STRING":do_agent_info(~p, ~p, ~p) entry~n", + %% [Domain, Address, Item]), case ets:lookup(snmpm_agent_table, {Domain, Address, Item}) of [{_, Val}] -> {ok, Val}; @@ -486,8 +509,8 @@ do_agent_info(Domain, Address, target_name = Item) -> {error, not_found} end; do_agent_info(Domain, Address, Item) -> - p(?MODULE_STRING":do_agent_info(~p, ~p, ~p) entry~n", - [Domain, Address, Item]), + %% p(?MODULE_STRING":do_agent_info(~p, ~p, ~p) entry~n", + %% [Domain, Address, Item]), case do_agent_info(Domain, Address, target_name) of {ok, TargetName} -> agent_info(TargetName, Item); @@ -1287,36 +1310,6 @@ verify_options(Opts, Mandatory) -> verify_mandatory_options(Opts, Mandatory), verify_options(Opts). -%% mandatory() -> [mand()] -%% mand() -> atom() | {atom, [atom()]} -verify_mandatory_options(_Opts, []) -> - ok; -verify_mandatory_options(Opts, [Mand|Mands]) -> - verify_mandatory_option(Opts, Mand), - verify_mandatory_options(Opts, Mands). - -verify_mandatory_option(Opts, {Mand, MandSubOpts}) -> - ?d("verify_mandatory_option -> entry with" - "~n Mand: ~p" - "~n MandSubObjs: ~p", [Mand, MandSubOpts]), - case lists:keysearch(Mand, 1, Opts) of - {value, {Mand, SubOpts}} -> - verify_mandatory_options(SubOpts, MandSubOpts); - false -> - ?d("missing mandatory option: ~w [~p]", [Mand, MandSubOpts]), - error({missing_mandatory, Mand, MandSubOpts}) - end; -verify_mandatory_option(Opts, Mand) -> - ?d("verify_mandatory_option -> entry with" - "~n Mand: ~p", [Mand]), - case lists:keymember(Mand, 1, Opts) of - true -> - ok; - false -> - ?d("missing mandatory option: ~w", [Mand]), - error({missing_mandatory, Mand}) - end. - verify_options([]) -> ?d("verify_options -> done", []), ok; @@ -1632,7 +1625,38 @@ verify_verbosity(Verbosity) -> _ -> error({invalid_verbosity, Verbosity}) end. + +%% mandatory() -> [mand()] +%% mand() -> atom() | {atom, [atom()]} +verify_mandatory_options(_Opts, []) -> + ok; +verify_mandatory_options(Opts, [Mand|Mands]) -> + verify_mandatory_option(Opts, Mand), + verify_mandatory_options(Opts, Mands). + +verify_mandatory_option(Opts, {Mand, MandSubOpts}) -> + ?d("verify_mandatory_option -> entry with" + "~n Mand: ~p" + "~n MandSubObjs: ~p", [Mand, MandSubOpts]), + case lists:keysearch(Mand, 1, Opts) of + {value, {Mand, SubOpts}} -> + verify_mandatory_options(SubOpts, MandSubOpts); + false -> + ?d("missing mandatory option: ~w [~p]", [Mand, MandSubOpts]), + error({missing_mandatory, Mand, MandSubOpts}) + end; +verify_mandatory_option(Opts, Mand) -> + ?d("verify_mandatory_option -> entry with" + "~n Mand: ~p", [Mand]), + case lists:keymember(Mand, 1, Opts) of + true -> + ok; + false -> + ?d("missing mandatory option: ~w", [Mand]), + error({missing_mandatory, Mand}) + end. + %% ------------------------------------------------------------------------ init_manager_config([]) -> @@ -1654,69 +1678,10 @@ init_agent_default() -> {version, v2}, % MPModel {sec_model, v2c}, % SecModel {sec_name, "initial"}, % SecName - {sec_level, noAuthPriv}, % SecLevel + {sec_level, noAuthNoPriv}, % SecLevel {community, "all-rights"}], % Community do_update_agent_info(default_agent, AgentDefaultConfig). -%% %% Port -%% init_agent_default(port, ?DEFAULT_AGENT_PORT), - -%% %% Timeout -%% init_agent_default(timeout, 10000), - -%% %% Max message (packet) size -%% init_agent_default(max_message_size, 484), - -%% %% MPModel -%% init_agent_default(version, v2), - -%% %% SecModel -%% init_agent_default(sec_model, v2c), - -%% %% SecName -%% init_agent_default(sec_name, "initial"), - -%% %% SecLevel -%% init_agent_default(sec_level, noAuthNoPriv), - -%% %% Community -%% init_agent_default(community, "all-rights"), -%% ok. - - -%% init_agent_default(Item, Val) when Item =/= user_id -> -%% case do_update_agent_info(default_agent, Item, Val) of -%% ok -> -%% ok; -%% {error, Reason} -> -%% error(Reason) -%% end. - -%% read_agents_config_file(Dir) -> -%% Verify = fun check_agent_config2/1, -%% case read_file(Dir, "agents.conf", Verify, []) of -%% {ok, Conf} -> -%% Conf; -%% Error -> -%% ?vlog("agent config error: ~p", [Error]), -%% throw(Error) -%% end. - -%% check_agent_config2(Agent) -> -%% case (catch check_agent_config(Agent)) of -%% {ok, {UserId, TargetName, Conf, Version}} -> -%% {ok, Vsns} = system_info(versions), -%% case lists:member(Version, Vsns) of -%% true -> -%% {ok, {UserId, TargetName, Conf}}; -%% false -> -%% error({version_not_supported_by_manager, -%% Version, Vsns}) -%% end; -%% Err -> -%% throw(Err) -%% end. - read_agents_config_file(Dir) -> Order = fun snmp_conf:no_order/2, Check = fun check_agent_config/2, @@ -1739,21 +1704,35 @@ check_agent_config(Agent, State) -> %% For backward compatibility check_agent_config( + {UserId, TargetName, Community, Domain, Addr, + EngineId, Timeout, MaxMessageSize, + Version, SecModel, SecName, SecLevel}) when is_atom(Domain) -> + check_agent_config( + UserId, TargetName, Community, Domain, Addr, + EngineId, Timeout, MaxMessageSize, + Version, SecModel, SecName, SecLevel); +check_agent_config( {UserId, TargetName, Community, Ip, Port, EngineId, Timeout, MaxMessageSize, - Version, SecModel, SecName, SecLevel}) -> + Version, SecModel, SecName, SecLevel}) when is_integer(Port) -> Domain = default_transport_domain(), - Addr = fix_address(Domain, {Ip, Port}), + Addr = {Ip, Port}, check_agent_config( UserId, TargetName, Community, Domain, Addr, EngineId, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel); check_agent_config( + {_UserId, _TargetName, _Community, Domain, Addr, + _EngineId, _Timeout, _MaxMessageSize, + _Version, _SecModel, _SecName, _SecLevel}) -> + error({bad_address, {Domain, Addr}}); +check_agent_config( {UserId, TargetName, Community, Domain, Ip, Port, EngineId, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel}) -> + Addr = {Ip, Port}, check_agent_config( - UserId, TargetName, Community, Domain, {Ip, Port}, + UserId, TargetName, Community, Domain, Addr, EngineId, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel); check_agent_config(Agent) -> @@ -1776,7 +1755,7 @@ check_agent_config( Conf = [{reg_type, target_name}, {tdomain, Domain}, - {taddress, Addr}, + {taddress, fix_address(Domain, Addr)}, {community, Comm}, {engine_id, EngineId}, {timeout, Timeout}, @@ -1860,7 +1839,11 @@ verify_agent_config( {TD, VerifiedConf}; _ -> %% Insert tdomain since it is missing - TD = default_transport_domain(), + %% Note: not default_transport_domain() since + %% taddress is the new format hence the application + %% should be tdomain aware and therefore addresses + %% on the Domain, Addr format should be used and understood. + TD = transportDomainUdpIpv4, {TD, [{tdomain, TD}|VerifiedConf]} end, case snmp_conf:check_address(TDomain, Address, 0) of @@ -1946,16 +1929,6 @@ verify_agent_entry(Item, _) -> -%% read_users_config_file(Dir) -> -%% Verify = fun check_user_config/1, -%% case read_file(Dir, "users.conf", Verify, []) of -%% {ok, Conf} -> -%% Conf; -%% Error -> -%% ?vlog("failure reading users config file: ~n ~p", [Error]), -%% throw(Error) -%% end. - read_users_config_file(Dir) -> Order = fun snmp_conf:no_order/2, Check = fun (User, State) -> {check_user_config(User), State} end, @@ -2074,14 +2047,6 @@ verify_default_agent_config(Conf) -> error({bad_default_agent_config, Error}) end. -%% read_usm_config_file(Dir) -> -%% Verify = fun check_usm_user_config/1, -%% case read_file(Dir, "usm.conf", Verify, []) of -%% {ok, Conf} -> -%% Conf; -%% Error -> -%% throw(Error) -%% end. read_usm_config_file(Dir) -> Order = fun snmp_conf:no_order/2, @@ -2268,24 +2233,6 @@ is_crypto_supported(Func) -> snmp_misc:is_crypto_supported(Func). -%% read_manager_config_file(Dir) -> -%% Verify = fun check_manager_config/1, -%% case read_file(Dir, "manager.conf", Verify) of -%% {ok, Conf} -> -%% ?d("read_manager_config_file -> ok: " -%% "~n Conf: ~p", [Conf]), -%% %% If the address is not specified, then we assume -%% %% it should be the local host. -%% %% If the address is not possible to determine -%% %% that way, then we give up... -%% verify_mandatory(Conf, [port,engine_id,max_message_size]), -%% ensure_config(default_manager_config(), Conf); -%% %% check_mandatory_manager_config(Conf), -%% %% ensure_manager_config(Conf); -%% Error -> -%% throw(Error) -%% end. - read_manager_config_file(Dir) -> Order = fun order_manager_config/2, Check = fun check_manager_config/2, @@ -2296,13 +2243,15 @@ read_manager_config_file(Dir) -> %% it should be the local host. %% If the address is not possible to determine %% that way, then we give up... - verify_mandatory(Conf, [port,engine_id,max_message_size]), + verify_someof(Conf, [port, transports]), + verify_mandatory(Conf, [engine_id, max_message_size]), default_manager_config(Conf). default_manager_config(Conf) -> - %% Ensure address of right family - case lists:keyfind(address, 1, Conf) of + %% Ensure valid transports entry + case lists:keyfind(transports, 1, Conf) of false -> + {port, Port} = lists:keyfind(port, 1, Conf), Domain = case lists:keyfind(domain, 1, Conf) of false -> @@ -2311,55 +2260,77 @@ default_manager_config(Conf) -> D end, Family = snmp_conf:tdomain_to_family(Domain), - {ok, HostName} = inet:gethostname(), - case inet:getaddr(HostName, Family) of + {ok, Hostname} = inet:gethostname(), + case inet:getaddr(Hostname, Family) of {ok, Address} -> - [{address, Address} | Conf]; + lists:sort( + fun order_manager_config/2, + [{transports, [{Domain, {Address, Port}}]} | Conf]); {error, _Reason} -> ?d("default_manager_config -> " "failed getting ~w address for ~s:~n" - " _Reason: ~p", [Family, HostName, _Reason]), + " _Reason: ~p", [Family, Hostname, _Reason]), Conf end; _ -> Conf end. -default_manager_config() -> - {ok, HostName} = inet:gethostname(), - case inet:getaddr(HostName, inet) of - {ok, A} -> - [{address, tuple_to_list(A)}]; - {error, _Reason} -> - ?d("default_manager_config -> failed getting address: " - "~n _Reason: ~p", [_Reason]), - [] - end. - order_manager_config(EntryA, EntryB) -> - snmp_conf:keyorder(1, EntryA, EntryB, [domain]). - -check_manager_config({domain, D}, _Domain) -> - {snmp_conf:check_domain(D), D}; -check_manager_config({address = Tag, Ip}, D) -> - Domain = - case D of - undefined -> - default_transport_domain(); - _ -> - D - end, + snmp_conf:keyorder(1, EntryA, EntryB, [domain, port]). + +check_manager_config(Entry, undefined) -> + check_manager_config(Entry, {default_transport_domain(), undefined}); +check_manager_config({domain, Domain}, {_, Port}) -> + {snmp_conf:check_domain(Domain), {Domain, Port}}; +check_manager_config({port, Port}, {Domain, _}) -> + {ok = snmp_conf:check_port(Port), {Domain, Port}}; +check_manager_config({address, _}, {_, undefined}) -> + error({missing_mandatory, port}); +check_manager_config({address = Tag, Ip} = Entry, {Domain, Port} = State) -> {case snmp_conf:check_ip(Domain, Ip) of ok -> - ok; + [Entry, + {transports, [{Domain, {Ip, Port}}]}]; {ok, FixedIp} -> - {ok, {Tag, FixedIp}} - end, Domain}; -check_manager_config(Entry, Domain) -> - {check_manager_config(Entry), Domain}. + [{Tag, FixedIp}, + {transports, [{Domain, {FixedIp, Port}}]}] + end, State}; +check_manager_config({transports = Tag, Transports}, {_, Port} = State) + when is_list(Transports) -> + CheckedTransports = + [case Transport of + {Domain, Address} -> + case + case Port of + undefined -> + snmp_conf:check_address(Domain, Address); + _ -> + snmp_conf:check_address(Domain, Address, Port) + end + of + ok -> + Transport; + {ok, FixedAddress} -> + {Domain, FixedAddress} + end; + _Domain when Port =:= undefined-> + error({missing_mandatory, port}); + Domain -> + Family = snmp_conf:tdomain_to_family(Domain), + {ok, Hostname} = inet:gethostname(), + case inet:getaddr(Hostname, Family) of + {ok, IpAddr} -> + {Domain, {IpAddr, Port}}; + {error, _} -> + error({bad_address, {Domain, Hostname}}) + end + end + || Transport <- Transports], + {{ok, {Tag, CheckedTransports}}, State}; +check_manager_config(Entry, State) -> + {check_manager_config(Entry), State}. -check_manager_config({port, Port}) -> - snmp_conf:check_port(Port); check_manager_config({engine_id, EngineID}) -> snmp_conf:check_string(EngineID); check_manager_config({max_message_size, Max}) -> @@ -2368,45 +2339,6 @@ check_manager_config(Conf) -> error({unknown_config, Conf}). -%% check_mandatory_manager_config(Conf) -> -%% Mand = [port, engine_id, max_message_size], -%% check_mandatory_manager_config(Mand, Conf). - -%% check_mandatory_manager_config([], _Conf) -> -%% ok; -%% check_mandatory_manager_config([Item|Mand], Conf) -> -%% case lists:keysearch(Item, 1, Conf) of -%% false -> -%% error({missing_mandatory_manager_config, Item}); -%% _ -> -%% check_mandatory_manager_config(Mand, Conf) -%% end. - - -%% ensure_manager_config(Confs) -> -%% ensure_manager_config(Confs, default_manager_config()). - -%% ensure_manager_config(Confs, []) -> -%% Confs; -%% ensure_manager_config(Confs, [{Key,_} = DefKeyVal|Defs]) -> -%% case lists:keysearch(Key, 1, Confs) of -%% false -> -%% ensure_manager_config([DefKeyVal|Confs], Defs); -%% {value, _Conf} -> -%% ensure_manager_config(Confs, Defs) -%% end. - -% ensure_manager_config([], Defs, Confs) -> -% Confs ++ Defs; -% ensure_manager_config(Confs0, [{Key, DefVal}|Defs], Acc) -> -% case lists:keysearch(Key, 1, Confs0) of -% false -> -% ensure_manager_config(Confs0, Defs, [{Key, DefVal}|Acc]); -% {value, Conf} -> -% Confs = lists:keydelete(Key, 1, Confs0), -% ensure_manager_config(Confs, Defs, [Conf|Acc]) -% end. - read_file(Dir, FileName, Order, Check, Default) -> try snmp_conf:read(filename:join(Dir, FileName), Order, Check) catch @@ -2424,87 +2356,6 @@ read_file(Dir, FileName, Order, Check) -> erlang:raise(throw, Error, erlang:get_stacktrace()) end. - - - - - -%% read_file(Dir, FileName, Verify, Default) -> -%% File = filename:join(Dir, FileName), -%% case file:read_file_info(File) of -%% {ok, _} -> -%% read_file(File, Verify); -%% {error, Reason} -> -%% ?vlog("failed reading config from ~s: ~p", [FileName, Reason]), -%% {ok, Default} -%% end. - -%% read_file(Dir, FileName, Verify) -> -%% File = filename:join(Dir, FileName), -%% case file:read_file_info(File) of -%% {ok, _} -> -%% read_file(File, Verify); -%% {error, Reason} -> -%% error_msg("failed reading config from ~s: ~p", [FileName, Reason]), -%% {error, {failed_reading, FileName, Reason}} -%% end. - -%% read_file(File, Verify) -> -%% Check = fun (Config, State) -> {Verify(Config), State} end, -%% try snmp_conf:read(File, Check) of -%% Conf -> -%% ?vtrace("read_file -> read ok" -%% "~n Conf: ~p", [Conf]), -%% {ok, Conf} -%% catch -%% Error -> -%% ?vtrace("read_file -> read failed:" -%% "~n Error: ~p", [Error]), -%% Error -%% end. - -%% XXX remove - -%% read_file(Dir, FileName, Check, Default) -> -%% File = filename:join(Dir, FileName), -%% case file:read_file_info(File) of -%% {ok, _} -> -%% case (catch do_read(File, Check)) of -%% {ok, Conf} -> -%% {ok, Conf}; -%% Error -> -%% ?vtrace("read_file -> read failed:" -%% "~n Error: ~p", [Error]), -%% Error -%% end; -%% {error, Reason} -> -%% ?vlog("failed reading config from ~s: ~p", [FileName, Reason]), -%% {ok, Default} -%% end. - -%% read_file(Dir, FileName, Check) -> -%% File = filename:join(Dir, FileName), -%% case file:read_file_info(File) of -%% {ok, _} -> -%% case (catch do_read(File, Check)) of -%% {ok, Conf} -> -%% ?vtrace("read_file -> read ok" -%% "~n Conf: ~p", [Conf]), -%% {ok, Conf}; -%% Error -> -%% ?vtrace("read_file -> read failed:" -%% "~n Error: ~p", [Error]), -%% Error -%% end; -%% {error, Reason} -> -%% error_msg("failed reading config from ~s: ~p", [FileName, Reason]), -%% {error, {failed_reading, FileName, Reason}} -%% end. - -%% do_read(File, Check) -> -%% {ok, snmp_conf:read(File, Check)}. - - %%-------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | @@ -3580,6 +3431,5 @@ error_msg(F, A) -> %% p(F) -> %% p(F, []). -p(F, A) -> - io:format("~w:" ++ F ++ "~n", [?MODULE | A]). - +%% p(F, A) -> +%% io:format("~w:" ++ F ++ "~n", [?MODULE | A]). diff --git a/lib/snmp/src/manager/snmpm_net_if.erl b/lib/snmp/src/manager/snmpm_net_if.erl index 860b0b83dd..cb72871177 100644 --- a/lib/snmp/src/manager/snmpm_net_if.erl +++ b/lib/snmp/src/manager/snmpm_net_if.erl @@ -17,7 +17,9 @@ %% %CopyrightEnd% %% +-ifndef(snmpm_net_if_mt). -module(snmpm_net_if). +-endif. -behaviour(gen_server). -behaviour(snmpm_network_interface). @@ -59,8 +61,7 @@ { server, note_store, - domain, - sock, + transports = [], mpd_state, log, irb = auto, % auto | {user, integer()} @@ -68,6 +69,9 @@ filter }). +-record(transport, + {socket, + domain = snmpUDPDomain}). -define(DEFAULT_FILTER_MODULE, snmpm_net_if_filter). -define(DEFAULT_FILTER_OPTS, [{module, ?DEFAULT_FILTER_MODULE}]). @@ -147,6 +151,64 @@ filter_reset(Pid) -> %%%------------------------------------------------------------------- +%%% Multi-thread manager +%%%------------------------------------------------------------------- + +-ifdef(snmpm_net_if_mt). + +%% This function is called through the macro below to +%% (in the not multithreaded case) avoid creating the +%% Failer/4 fun, and to avoid calling the Worker through a fun +%% (now it shall not be a fun, just a code snippet). + +worker(Worker, Failer, #state{log = Log} = State) -> + Verbosity = get(verbosity), + spawn_opt( + fun () -> + try + put(sname, mnifw), + put(verbosity, Verbosity), + NewState = + case do_reopen_log(Log) of + Log -> + State; + NewLog -> + State#state{log = NewLog} + end, + Worker(NewState) + of + Result -> + %% Winds up in handle_info {'DOWN', ...} + erlang:exit({net_if_worker, Result}) + catch + Class:Reason -> + %% Winds up in handle_info {'DOWN', ...} + erlang:exit( + {net_if_worker, Failer, + Class, Reason, erlang:get_stacktrace()}) + end + end, + [monitor]). +-define( + worker(S, Worker, Failer, State), + begin + worker( + fun (S) -> begin Worker end end, + begin Failer end, + (State)) + end). + +-else. + +-define( + worker(S, Worker, _Failer, State), + begin (S) = (State), begin Worker end end). + +-endif. + + + +%%%------------------------------------------------------------------- %%% Callback functions from gen_server %%%------------------------------------------------------------------- @@ -161,12 +223,19 @@ init([Server, NoteStore]) -> ?d("init -> entry with" "~n Server: ~p" "~n NoteStore: ~p", [Server, NoteStore]), - case (catch do_init(Server, NoteStore)) of + try do_init(Server, NoteStore) + catch {error, Reason} -> - {stop, Reason}; - {ok, State} -> - {ok, State} + {stop, Reason} end. + +-ifdef(snmpm_net_if_mt). +%% This should really be protected, but it also needs to +%% be writable for the worker processes, so... +-define(inform_table_opts, [set, public, named_table, {keypos, 1}]). +-else. +-define(inform_table_opts, [set, protected, named_table, {keypos, 1}]). +-endif. do_init(Server, NoteStore) -> process_flag(trap_exit, true), @@ -176,18 +245,18 @@ do_init(Server, NoteStore) -> process_flag(priority, Prio), %% -- Create inform request table -- - ets:new(snmpm_inform_request_table, - [set, protected, named_table, {keypos, 1}]), + ets:new(snmpm_inform_request_table, ?inform_table_opts), %% -- Verbosity -- {ok, Verbosity} = snmpm_config:system_info(net_if_verbosity), - put(sname,mnif), - put(verbosity,Verbosity), + put(sname, mnif), + put(verbosity, Verbosity), ?vlog("starting", []), %% -- MPD -- {ok, Vsns} = snmpm_config:system_info(versions), MpdState = snmpm_mpd:init(Vsns), + ?vdebug("MpdState: ~w", [MpdState]), %% -- Module dependent options -- {ok, Opts} = snmpm_config:system_info(net_if_options), @@ -196,21 +265,6 @@ do_init(Server, NoteStore) -> {ok, IRB} = snmpm_config:system_info(net_if_irb), IrGcRef = irgc_start(IRB), - %% -- Socket -- - SndBuf = get_opt(Opts, sndbuf, default), - RecBuf = get_opt(Opts, recbuf, default), - BindTo = get_opt(Opts, bind_to, false), - NoReuse = get_opt(Opts, no_reuse, false), - {ok, Port} = snmpm_config:system_info(port), - Domain = - case snmpm_config:system_info(domain) of - {ok, D} -> - D; - _ -> - snmpm_config:default_transport_domain() - end, - {ok, Sock} = do_open_port(Port, SndBuf, RecBuf, Domain, BindTo, NoReuse), - %% Flow control -- FilterOpts = get_opt(Opts, filter, []), FilterMod = create_filter(FilterOpts), @@ -219,83 +273,104 @@ do_init(Server, NoteStore) -> %% -- Audit trail log --- {ok, ATL} = snmpm_config:system_info(audit_trail_log), Log = do_init_log(ATL), + ?vdebug("Log: ~w", [Log]), + + {ok, DomainAddresses} = snmpm_config:system_info(transports), + ?vdebug("DomainAddresses: ~w",[DomainAddresses]), + CommonSocketOpts = common_socket_opts(Opts), + BindTo = get_opt(Opts, bind_to, false), + case + [begin + {IpPort, SocketOpts} = + socket_params(Domain, Address, BindTo, CommonSocketOpts), + Socket = socket_open(IpPort, SocketOpts), + #transport{socket = Socket, domain = Domain} + end || {Domain, Address} <- DomainAddresses] + of + [] -> + ?vinfo("No transports configured: ~p", [DomainAddresses]), + throw({error, {no_transports,DomainAddresses}}); + Transports -> + %% -- Initiate counters --- + init_counters(), + + %% -- We are done --- + State = #state{ + server = Server, + note_store = NoteStore, + mpd_state = MpdState, + transports = Transports, + log = Log, + irb = IRB, + irgc = IrGcRef, + filter = FilterMod}, + ?vdebug("started", []), + {ok, State} + end. - %% -- Initiate counters --- - init_counters(), - - %% -- We are done --- - State = #state{server = Server, - note_store = NoteStore, - mpd_state = MpdState, - domain = Domain, - sock = Sock, - log = Log, - irb = IRB, - irgc = IrGcRef, - filter = FilterMod}, - ?vdebug("started", []), - {ok, State}. - - -%% Open port -do_open_port(Port, SendSz, RecvSz, Domain, BindTo, NoReuse) -> - ?vtrace("do_open_port -> entry with~n" - " Port: ~p~n" - " SendSz: ~p~n" - " RecvSz: ~p~n" - " Domain: ~p~n" - " BindTo: ~p~n" - " NoReuse: ~p", - [Port, SendSz, RecvSz, Domain, BindTo, NoReuse]), - IpOpts1 = bind_to(BindTo), - IpOpts2 = no_reuse(NoReuse), - IpOpts3 = recbuf(RecvSz), - IpOpts4 = sndbuf(SendSz), - IpOpts = - [binary, - snmp_conf:tdomain_to_family(Domain) | - IpOpts1 ++ IpOpts2 ++ IpOpts3 ++ IpOpts4], - OpenRes = - case init:get_argument(snmpm_fd) of - {ok, [[FdStr]]} -> - Fd = list_to_integer(FdStr), - gen_udp:open(0, [{fd, Fd}|IpOpts]); - error -> - gen_udp:open(Port, IpOpts) - end, - case OpenRes of +socket_open(IpPort, SocketOpts) -> + ?vtrace("socket_open -> entry with~n" + " IpPort: ~p~n" + " SocketOpts: ~p", [IpPort, SocketOpts]), + case gen_udp:open(IpPort, SocketOpts) of {error, _} = Error -> throw(Error); - OK -> - OK + {ok, Socket} -> + Socket end. -bind_to(true) -> - case snmpm_config:system_info(address) of - {ok, Addr} when is_list(Addr) -> - [{ip, list_to_tuple(Addr)}]; - {ok, Addr} -> - [{ip, Addr}]; +socket_params(Domain, {IpAddr, IpPort}, BindTo, CommonSocketOpts) -> + Family = snmp_conf:tdomain_to_family(Domain), + SocketOpts = + case Family of + inet6 -> + [Family, {ipv6_v6only, true} | CommonSocketOpts]; + Family -> + [Family | CommonSocketOpts] + end, + case Family of + inet -> + case init:get_argument(snmp_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + case BindTo of + true -> + {IpPort, [{ip, IpAddr}, {fd, Fd} | SocketOpts]}; + _ -> + {0, [{fd, Fd} | SocketOpts]} + end; + error -> + {IpPort, [{ip, IpAddr} | SocketOpts]} + end; _ -> - [] - end; -bind_to(_) -> - []. - -no_reuse(false) -> - [{reuseaddr, true}]; -no_reuse(_) -> - []. - -recbuf(default) -> - []; -recbuf(Sz) -> - [{recbuf, Sz}]. + case BindTo of + true -> + {IpPort, [{ip, IpAddr} | SocketOpts]}; + _ -> + {IpPort, SocketOpts} + end + end. -sndbuf(default) -> - []; -sndbuf(Sz) -> - [{sndbuf, Sz}]. +common_socket_opts(Opts) -> + [binary + | case get_opt(Opts, sndbuf, default) of + default -> + []; + Sz -> + [{sndbuf, Sz}] + end ++ + case get_opt(Opts, recbuf, default) of + default -> + []; + Sz -> + [{sndbuf, Sz}] + end ++ + case get_opt(Opts, no_reuse, false) of + false -> + [{reuseaddr, true}]; + _ -> + [] + end]. create_filter(Opts) when is_list(Opts) -> @@ -310,6 +385,10 @@ create_filter(BadOpts) -> throw({error, {bad_filter_opts, BadOpts}}). +%% ---------------------------------------------------------------------- +%% Audit Trail Logger +%% ---------------------------------------------------------------------- + %% Open log do_init_log(false) -> ?vtrace("do_init_log(false) -> entry", []), @@ -330,24 +409,69 @@ do_init_log(true) -> Function = increment_counter, Args = [atl_seqno, Initial, Max], SeqNoGen = {Module, Function, Args}, - case snmp_log:create(Name, File, - SeqNoGen, Size, Repair, true) of + case snmp_log:create( + Name, File, SeqNoGen, Size, Repair, true) of {ok, Log} -> ?vdebug("log created: ~w", [Log]), - {Log, Type}; + {Name, Log, Type}; {error, Reason} -> throw({error, {failed_create_audit_log, Reason}}) end; _ -> case snmp_log:create(Name, File, Size, Repair, true) of {ok, Log} -> - {Log, Type}; + ?vdebug("log created: ~w", [Log]), + {Name, Log, Type}; {error, Reason} -> throw({error, {failed_create_audit_log, Reason}}) end end. - +-ifdef(snmpm_net_if_mt). +do_reopen_log(undefined) -> + undefined; +do_reopen_log({Name, Log, Type}) -> + case snmp_log:open(Name, Log) of + {ok, NewLog} -> + {Name, NewLog, Type}; + {error, Reason} -> + warning_msg( + "NetIf worker ~p failed to open ATL:~n" + " ~p", [self(), Reason]), + undefined + end. +-endif. + +%% Close log +do_close_log(undefined) -> + ok; +do_close_log({_Name, Log, _Type}) -> + (catch snmp_log:sync(Log)), + (catch snmp_log:close(Log)), + ok; +do_close_log(_) -> + ok. + +%% Log +logger(undefined, _Type, _Domain, _Addr) -> + fun(_) -> + ok + end; +logger({_Name, Log, Types}, Type, Domain, Addr) -> + case lists:member(Type, Types) of + true -> + AddrString = + iolist_to_binary(snmp_conf:mk_addr_string({Domain, Addr})), + fun(Msg) -> + snmp_log:log(Log, Msg, AddrString) + end; + false -> + fun(_) -> + ok + end + end. + + %%-------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | @@ -409,7 +533,7 @@ handle_cast({send_pdu, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo}, " Vsn: ~p~n" " MsgData: ~p~n" " Domain: ~p~n" - " Addr : ~p", [Pdu, Vsn, MsgData, Domain, Addr]), + " Addr: ~p", [Pdu, Vsn, MsgData, Domain, Addr]), maybe_process_extra_info(ExtraInfo), maybe_handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State), {noreply, State}; @@ -439,11 +563,20 @@ handle_cast(Msg, State) -> %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info( - {udp, Sock, Ip, Port, Bytes}, - #state{sock = Sock, domain = Domain} = State) -> - ?vlog("received ~w bytes from ~p:~p [~w]", [size(Bytes), Ip, Port, Sock]), - maybe_handle_recv_msg(Domain, {Ip, Port}, Bytes, State), - {noreply, State}; + {udp, Socket, IpAddr, IpPort, Bytes}, + #state{transports = Transports} = State) -> + Size = byte_size(Bytes), + case lists:keyfind(Socket, #transport.socket, Transports) of + #transport{socket = Socket, domain = Domain} -> + ?vlog("received ~w bytes from ~p:~p [~w]", + [Size, IpAddr, IpPort, Socket]), + maybe_handle_recv_msg(Domain, {IpAddr, IpPort}, Bytes, State), + {noreply, State}; + false -> + warning_msg("Received ~w bytes on unknown port: ~p from ~s", + [Size, Socket, format_address({IpAddr, IpPort})]), + {noreply, State} + end; handle_info(inform_response_gc, State) -> ?vlog("received inform_response_gc message", []), @@ -456,11 +589,42 @@ handle_info({disk_log, _Node, Log, Info}, State) -> State2 = handle_disk_log(Log, Info, State), {noreply, State2}; +handle_info({'DOWN', _, _, _, _} = Info, State) -> + handle_info_down(Info, State); + handle_info(Info, State) -> + handle_info_unknown(Info, State). + + +handle_info_unknown(Info, State) -> warning_msg("received unknown info: ~n~p", [Info]), {noreply, State}. +-ifdef(snmpm_net_if_mt). +handle_info_down( + {'DOWN', _MRef, process, _Pid, + {net_if_worker, _Result}}, + State) -> + ?vdebug("received DOWN message from net_if worker [~w]: " + "~n Result: ~p", [_Pid, _Result]), + {noreply, State}; +handle_info_down( + {'DOWN', _MRef, process, Pid, + {net_if_worker, Failer, Class, Reason, Stacktrace} = _ExitStatus}, + State) -> + ?vdebug("received DOWN message from net_if worker [~w]: " + "~n ExitStatus: ~p", [Pid, _ExitStatus]), + Failer(Pid, Class, Reason, Stacktrace), + {noreply, State}; +handle_info_down(Info, State) -> + handle_info_unknown(Info, State). +-else. +handle_info_down(Info, State) -> + handle_info_unknown(Info, State). +-endif. + + %%-------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server @@ -474,68 +638,12 @@ terminate(Reason, #state{log = Log, irgc = IrGcRef}) -> ok. -do_close_log({Log, _Type}) -> - (catch snmp_log:sync(Log)), - (catch snmp_log:close(Log)), - ok; -do_close_log(_) -> - ok. - - %%---------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%---------------------------------------------------------------------- -code_change({down, _Vsn}, OldState, downgrade_to_pre_4_14) -> - ?d("code_change(down, downgrade_to_pre_4_14) -> entry with" - "~n OldState: ~p", [OldState]), - #state{server = Server, - note_store = NoteStore, - sock = Sock, - mpd_state = MpdState, - log = {OldLog, Type}, - irb = IRB, - irgc = IRGC} = OldState, - NewLog = snmp_log:downgrade(OldLog), - State = - {state, Server, NoteStore, Sock, MpdState, {NewLog, Type}, IRB, IRGC}, - {ok, State}; - -code_change({down, _Vsn}, OldState, downgrade_to_pre_4_16) -> - ?d("code_change(down, downgrade_to_pre_4_16) -> entry with" - "~n OldState: ~p", [OldState]), - {OldLog, Type} = OldState#state.log, - NewLog = snmp_log:downgrade(OldLog), - State = OldState#state{log = {NewLog, Type}}, - {ok, State}; - -% upgrade -code_change(_Vsn, OldState, upgrade_from_pre_4_14) -> - ?d("code_change(up, upgrade_from_pre_4_14) -> entry with" - "~n OldState: ~p", [OldState]), - {state, Server, NoteStore, Sock, MpdState, {OldLog, Type}, IRB, IRGC} = - OldState, - NewLog = snmp_log:upgrade(OldLog), - State = #state{server = Server, - note_store = NoteStore, - sock = Sock, - mpd_state = MpdState, - log = {NewLog, Type}, - irb = IRB, - irgc = IRGC, - filter = ?DEFAULT_FILTER_MODULE}, - {ok, State}; - -code_change(_Vsn, OldState, upgrade_from_pre_4_16) -> - ?d("code_change(up, upgrade_from_pre_4_16) -> entry with" - "~n OldState: ~p", [OldState]), - {OldLog, Type} = OldState#state.log, - NewLog = snmp_log:upgrade(OldLog), - State = OldState#state{log = {NewLog, Type}}, - {ok, State}; - code_change(_Vsn, State, _Extra) -> ?d("code_change -> entry with" "~n Vsn: ~p" @@ -548,80 +656,88 @@ code_change(_Vsn, State, _Extra) -> %%% Internal functions %%%------------------------------------------------------------------- -maybe_handle_recv_msg( +maybe_handle_recv_msg(Domain, Addr, Bytes, State) -> + ?worker( + S, maybe_handle_recv_msg_mt(Domain, Addr, Bytes, S), + fun (Pid, Class, Reason, Stacktrace) -> + warning_msg( + "Worker process (~p) terminated " + "while processing (incomming) message from %s:~n" + "~w:~w at ~p", + [Pid, snmp_conf:mk_addr_string({Domain, Addr}), + Class, Reason, Stacktrace]) + end, + State). + +maybe_handle_recv_msg_mt( Domain, Addr, Bytes, - #state{filter = FilterMod, domain = ManagerDomain} = State) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), + #state{filter = FilterMod, transports = Transports} = State) -> + {Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}), case (catch FilterMod:accept_recv(Arg1, Arg2)) of false -> %% Drop the received packet - inc(netIfMsgInDrops), - ok; + inc(netIfMsgInDrops); _ -> handle_recv_msg(Domain, Addr, Bytes, State) - end. + end, + ok. handle_recv_msg(Domain, Addr, Bytes, #state{server = Pid}) when is_binary(Bytes) andalso (size(Bytes) =:= 0) -> - Pid ! {snmp_error, {empty_message, Domain, Addr}, Domain, Addr}, - ok; - + Pid ! {snmp_error, {empty_message, Domain, Addr}, Domain, Addr}; +%% handle_recv_msg( Domain, Addr, Bytes, - #state{server = Pid, - note_store = NoteStore, - mpd_state = MpdState, - log = Log} = State) -> + #state{ + server = Pid, + note_store = NoteStore, + mpd_state = MpdState, + log = Log} = State) -> Logger = logger(Log, read, Domain, Addr), - case (catch snmpm_mpd:process_msg(Bytes, Domain, Addr, - MpdState, NoteStore, Logger)) of + case (catch snmpm_mpd:process_msg( + Bytes, Domain, Addr, MpdState, NoteStore, Logger)) of {ok, Vsn, Pdu, MS, ACM} -> - maybe_handle_recv_pdu(Domain, Addr, Vsn, Pdu, MS, ACM, - Logger, State); + maybe_handle_recv_pdu( + Domain, Addr, Vsn, Pdu, MS, ACM, Logger, State); {discarded, Reason, Report} -> ?vdebug("discarded: ~p", [Reason]), ErrorInfo = {failed_processing_message, Reason}, Pid ! {snmp_error, ErrorInfo, Domain, Addr}, - maybe_udp_send(Domain, Addr, Report, State), - ok; + maybe_udp_send(Domain, Addr, Report, State); {discarded, Reason} -> ?vdebug("discarded: ~p", [Reason]), ErrorInfo = {failed_processing_message, Reason}, - Pid ! {snmp_error, ErrorInfo, Domain, Addr}, - ok; + Pid ! {snmp_error, ErrorInfo, Domain, Addr}; Error -> error_msg("processing of received message failed: " - "~n ~p", [Error]), - ok + "~n ~p", [Error]) end. maybe_handle_recv_pdu( Domain, Addr, Vsn, #pdu{type = Type} = Pdu, PduMS, ACM, Logger, - #state{filter = FilterMod, domain = ManagerDomain} = State) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), + #state{filter = FilterMod, transports = Transports} = State) -> + {Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}), case (catch FilterMod:accept_recv_pdu(Arg1, Arg2, Type)) of false -> - inc(netIfPduInDrops), - ok; + inc(netIfPduInDrops); _ -> handle_recv_pdu( Domain, Addr, Vsn, Pdu, PduMS, ACM, Logger, State) end; maybe_handle_recv_pdu( Domain, Addr, Vsn, Trap, PduMS, ACM, Logger, - #state{filter = FilterMod, domain = ManagerDomain} = State) + #state{filter = FilterMod, transports = Transports} = State) when is_record(Trap, trappdu) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), + {Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}), case (catch FilterMod:accept_recv_pdu(Arg1, Arg2, trappdu)) of false -> - inc(netIfPduInDrops), - ok; + inc(netIfPduInDrops); _ -> handle_recv_pdu( Domain, Addr, Vsn, Trap, PduMS, ACM, Logger, State) @@ -703,6 +819,19 @@ handle_inform_request( end. handle_inform_response(Ref, Domain, Addr, State) -> + ?worker( + S, handle_inform_response_mt(Ref, Domain, Addr, S), + fun (Pid, Class, Reason, Stacktrace) -> + warning_msg( + "Worker process (~p) terminated " + "while processing (outgoing) inform response for %s:~n" + "~w:~w at ~p", + [Pid, snmp_conf:mk_addr_string({Domain, Addr}), + Class, Reason, Stacktrace]) + end, + State). + +handle_inform_response_mt(Ref, Domain, Addr, State) -> Key = {Ref, Domain, Addr}, case ets:lookup(snmpm_inform_request_table, Key) of [{Key, _, {Vsn, ACM, RePdu}}] -> @@ -718,10 +847,11 @@ handle_inform_response(Ref, Domain, Addr, State) -> maybe_send_inform_response( RePdu, Vsn, ACM, Domain, Addr, Logger, - #state{server = Pid, - filter = FilterMod, - domain = ManagerDomain} = State) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), + #state{ + server = Pid, + filter = FilterMod, + transports = Transports} = State) -> + {Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}), case (catch FilterMod:accept_send_pdu( Arg1, Arg2, pdu_type_of(RePdu))) of @@ -737,8 +867,7 @@ maybe_send_inform_response( "~n Reason: ~p", [Reason]), ReqId = RePdu#pdu.request_id, ErrorInfo = {failed_generating_response, {RePdu, Reason}}, - Pid ! {snmp_error, ReqId, ErrorInfo, Domain, Addr}, - ok + Pid ! {snmp_error, ReqId, ErrorInfo, Domain, Addr} end end. @@ -771,24 +900,37 @@ irgc_stop(undefined) -> irgc_stop(Ref) -> (catch erlang:cancel_timer(Ref)). - -maybe_handle_send_pdu( +maybe_handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State) -> + ?worker( + S, maybe_handle_send_pdu_mt(Pdu, Vsn, MsgData, Domain, Addr, S), + fun (Pid, Class, Reason, Stacktrace) -> + warning_msg( + "Worker process (~p) terminated " + "while processing (outgoing) pdu for %s:~n" + "~w:~w at ~p", + [Pid, snmp_conf:mk_addr_string({Domain, Addr}), + Class, Reason, Stacktrace]) + end, + State). + +maybe_handle_send_pdu_mt( Pdu, Vsn, MsgData, Domain, Addr, - #state{filter = FilterMod, domain = ManagerDomain} = State) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), + #state{filter = FilterMod, transports = Transports} = State) -> + {Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}), case (catch FilterMod:accept_send_pdu(Arg1, Arg2, pdu_type_of(Pdu))) of false -> - inc(netIfPduOutDrops), - ok; + inc(netIfPduOutDrops); _ -> handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State) - end. + end, + ok. handle_send_pdu( Pdu, Vsn, MsgData, Domain, Addr, - #state{server = Pid, - note_store = NoteStore, - log = Log} = State) -> + #state{ + server = Pid, + note_store = NoteStore, + log = Log} = State) -> Logger = logger(Log, write, Domain, Addr), case (catch snmpm_mpd:generate_msg( Vsn, NoteStore, Pdu, MsgData, Logger)) of @@ -799,43 +941,58 @@ handle_send_pdu( ?vlog("PDU not sent: " "~n PDU: ~p" "~n Reason: ~p", [Pdu, Reason]), - Pid ! {snmp_error, Pdu, Reason}, - ok + Pid ! {snmp_error, Pdu, Reason} end. maybe_udp_send( Domain, Addr, Msg, - #state{sock = Sock, filter = FilterMod, domain = ManagerDomain}) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), + #state{filter = FilterMod, transports = Transports}) -> + To = {Domain, Addr}, + {Arg1, Arg2} = fix_filter_address(Transports, To), case (catch FilterMod:accept_send(Arg1, Arg2)) of false -> inc(netIfMsgOutDrops), ok; _ -> - %% XXX There should be some kind of lookup of socket - %% from transport domain here - {Ip, Port} = Addr, - udp_send(Sock, Ip, Port, Msg) + case select_transport_from_domain(Domain, Transports) of + false -> + error_msg( + "Can not find transport~n" + " size: ~p~n" + " to: ~s", + [sz(Msg), format_address(To)]); + #transport{socket = Socket} -> + udp_send(Socket, Addr, Msg) + end end. - - -udp_send(Sock, Ip, Port, Msg) -> - case (catch gen_udp:send(Sock, Ip, Port, Msg)) of + +udp_send(Sock, To, Msg) -> + {IpAddr, IpPort} = + case To of + {Domain, Addr} when is_atom(Domain) -> + Addr; + {_, P} = Addr when is_integer(P) -> + Addr + end, + try gen_udp:send(Sock, IpAddr, IpPort, Msg) of ok -> ?vdebug("sent ~w bytes to ~w:~w [~w]", - [sz(Msg), Ip, Port, Sock]), + [sz(Msg), IpAddr, IpPort, Sock]), ok; {error, Reason} -> - error_msg("failed sending message to ~p:~p: " - "~n ~p",[Ip, Port, Reason]); - Error -> - error_msg("failed sending message to ~p:~p: " - "~n ~p",[Ip, Port, Error]) + error_msg("failed sending message to ~p:~p:~n" + " ~p",[IpAddr, IpPort, Reason]) + catch + error:Error -> + error_msg("failed sending message to ~p:~p:~n" + " error:~p~n" + " ~p", + [IpAddr, IpPort, Error, erlang:get_stacktrace()]) end. sz(B) when is_binary(B) -> - size(B); + byte_size(B); sz(L) when is_list(L) -> length(L); sz(_) -> @@ -1025,26 +1182,45 @@ handle_set_log_type(State, _NewType) -> {State, {error, not_enabled}}. +select_transport_from_domain(Domain, Transports) when is_atom(Domain) -> + Pos = #transport.domain, + case lists:keyfind(Domain, Pos, Transports) of + #transport{domain = Domain} = Transport -> + Transport; + false when Domain == snmpUDPDomain -> + lists:keyfind(transportDomainUdpIpv4, Pos, Transports); + false when Domain == transportDomainUdpIpv4 -> + lists:keyfind(snmpUDPDomain, Pos, Transports); + false -> + false + end. + %% If the manager uses legacy snmpUDPDomain e.g has not set %% {domain, _}, then make sure snmpm_network_interface_filter %% gets legacy arguments to not break backwards compatibility. %% -fix_filter_address(snmpUDPDomain, {Domain, Addr}) - when Domain =:= snmpUDPDomain; - Domain =:= transportDomainUdpIpv4 -> - Addr; -fix_filter_address(_ManagerDomain, {Domain, _} = Address) - when is_atom(Domain) -> - Address; -fix_filter_address(snmpUDPDomain, {_, Port} = Addr) - when is_integer(Port) -> - Addr. +fix_filter_address(Transports, Address) -> + DefaultDomain = snmpm_config:default_transport_domain(), + case Transports of + [#transport{domain = DefaultDomain}, DefaultDomain] -> + case Address of + {Domain, Addr} when is_atom(Domain) -> + Addr; + {_, IpPort} = Addr when is_integer(IpPort) -> + Addr + end; + _ -> + Address + end. address(Domain, Addr) when is_atom(Domain) -> {Domain, Addr}; address(Ip, Port) when is_integer(Port) -> {snmpm_config:default_transport_domain(), {Ip, Port}}. +format_address(Address) -> + iolist_to_binary(snmp_conf:mk_addr_string(Address)). + %% ------------------------------------------------------------------- make_response_pdu(#pdu{request_id = ReqId, varbinds = Vbs}) -> @@ -1085,27 +1261,6 @@ t() -> %% ------------------------------------------------------------------- -logger(undefined, _Type, _Domain, _Addr) -> - fun(_) -> - ok - end; -logger({Log, Types}, Type, Domain, Addr) -> - case lists:member(Type, Types) of - true -> - AddrString = - iolist_to_binary(snmp_conf:mk_addr_string({Domain, Addr})), - fun(Msg) -> - snmp_log:log(Log, Msg, AddrString) - end; - false -> - fun(_) -> - ok - end - end. - - -%% ------------------------------------------------------------------- - %% info_msg(F, A) -> %% ?snmpm_info("NET-IF server: " ++ F, A). @@ -1130,10 +1285,11 @@ get_opt(Opts, Key, Def) -> %% ------------------------------------------------------------------- -get_info(#state{sock = Id}) -> +get_info(#state{transports = Transports}) -> ProcSize = proc_mem(self()), - PortInfo = get_port_info(Id), - [{process_memory, ProcSize}, {port_info, PortInfo}]. + [{process_memory, ProcSize} + | [{port_info, get_port_info(Socket)} + || #transport{socket = Socket} <- Transports]]. proc_mem(P) when is_pid(P) -> case (catch erlang:process_info(P, memory)) of @@ -1248,4 +1404,3 @@ call(Pid, Req, Timeout) -> cast(Pid, Msg) -> gen_server:cast(Pid, Msg). - diff --git a/lib/snmp/src/manager/snmpm_net_if_mt.erl b/lib/snmp/src/manager/snmpm_net_if_mt.erl index 2937f5cc87..62f6023657 100644 --- a/lib/snmp/src/manager/snmpm_net_if_mt.erl +++ b/lib/snmp/src/manager/snmpm_net_if_mt.erl @@ -17,1309 +17,7 @@ %% %CopyrightEnd% %% --module(snmpm_net_if_mt). - --behaviour(gen_server). --behaviour(snmpm_network_interface). - - -%% Network Interface callback functions --export([ - start_link/2, - stop/1, - send_pdu/6, % Backward compatibility - send_pdu/7, % Partly backward compatibility - send_pdu/8, % Backward compatibility - - inform_response/4, - - note_store/2, - - info/1, - verbosity/2, - %% system_info_updated/2, - get_log_type/1, set_log_type/2, - filter_reset/1 - ]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - code_change/3, terminate/2]). - --define(SNMP_USE_V3, true). --include("snmp_types.hrl"). --include("snmpm_internal.hrl"). --include("snmpm_atl.hrl"). --include("snmp_debug.hrl"). - -%% -define(VMODULE,"NET_IF"). --include("snmp_verbosity.hrl"). - --record(state, - { - server, - note_store, - domain, - sock, - mpd_state, - log, - irb = auto, % auto | {user, integer()} - irgc, - filter - }). - - --define(DEFAULT_FILTER_MODULE, snmpm_net_if_filter). --define(DEFAULT_FILTER_OPTS, [{module, ?DEFAULT_FILTER_MODULE}]). - --ifdef(snmp_debug). --define(GS_START_LINK(Args), - gen_server:start_link(?MODULE, Args, [{debug,[trace]}])). --else. --define(GS_START_LINK(Args), - gen_server:start_link(?MODULE, Args, [])). --endif. - - --define(IRGC_TIMEOUT, timer:minutes(5)). - --define(ATL_SEQNO_INITIAL, 1). --define(ATL_SEQNO_MAX, 2147483647). - - -%%%------------------------------------------------------------------- -%%% API -%%%------------------------------------------------------------------- -start_link(Server, NoteStore) -> - ?d("start_link -> entry with" - "~n Server: ~p" - "~n NoteStore: ~p", [Server, NoteStore]), - Args = [Server, NoteStore], - ?GS_START_LINK(Args). - -stop(Pid) -> - call(Pid, stop). - -send_pdu(Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port) -> - send_pdu( - Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port, ?DEFAULT_EXTRA_INFO). - -send_pdu(Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port, ExtraInfo) - when is_record(Pdu, pdu) -> - ?d("send_pdu -> entry with~n" - " Pid: ~p~n" - " Pdu: ~p~n" - " Vsn: ~p~n" - " MsgData: ~p~n" - " Domain/IP: ~p~n" - " Addr/Port : ~p", - [Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port]), - {Domain, Addr} = address(Domain_or_Ip, Addr_or_Port), - cast(Pid, {send_pdu, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo}). - -send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Ip, Port, ExtraInfo) -> - send_pdu(Pid, Pdu, Vsn, MsgData, Domain, {Ip, Port}, ExtraInfo). - -note_store(Pid, NoteStore) -> - call(Pid, {note_store, NoteStore}). - -inform_response(Pid, Ref, Domain_or_Ip, Addr_or_Port) -> - {Domain, Addr} = address(Domain_or_Ip, Addr_or_Port), - cast(Pid, {inform_response, Ref, Domain, Addr}). - -info(Pid) -> - call(Pid, info). - -verbosity(Pid, V) -> - call(Pid, {verbosity, V}). - -%% system_info_updated(Pid, What) -> -%% call(Pid, {system_info_updated, What}). - -get_log_type(Pid) -> - call(Pid, get_log_type). - -set_log_type(Pid, NewType) -> - call(Pid, {set_log_type, NewType}). - -filter_reset(Pid) -> - cast(Pid, filter_reset). - - -%%%------------------------------------------------------------------- -%%% Callback functions from gen_server -%%%------------------------------------------------------------------- - -%%-------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%%-------------------------------------------------------------------- -init([Server, NoteStore]) -> - ?d("init -> entry with" - "~n Server: ~p" - "~n NoteStore: ~p", [Server, NoteStore]), - case (catch do_init(Server, NoteStore)) of - {error, Reason} -> - {stop, Reason}; - {ok, State} -> - {ok, State} - end. - -do_init(Server, NoteStore) -> - process_flag(trap_exit, true), - - %% -- Prio -- - {ok, Prio} = snmpm_config:system_info(prio), - process_flag(priority, Prio), - - %% -- Create inform request table -- - %% This should really be protected, but it also needs to - %% be writable for the worker processes, so... - ets:new(snmpm_inform_request_table, - [set, public, named_table, {keypos, 1}]), - - %% -- Verbosity -- - {ok, Verbosity} = snmpm_config:system_info(net_if_verbosity), - put(sname, mnif), - put(verbosity, Verbosity), - ?vlog("starting", []), - - %% -- MPD -- - {ok, Vsns} = snmpm_config:system_info(versions), - MpdState = snmpm_mpd:init(Vsns), - ?vdebug("MpdState: ~w", [MpdState]), - - %% -- Module dependent options -- - {ok, Opts} = snmpm_config:system_info(net_if_options), - - %% -- Inform response behaviour -- - {ok, IRB} = snmpm_config:system_info(net_if_irb), - IrGcRef = irgc_start(IRB), - - %% -- Socket -- - SndBuf = get_opt(Opts, sndbuf, default), - RecBuf = get_opt(Opts, recbuf, default), - BindTo = get_opt(Opts, bind_to, false), - NoReuse = get_opt(Opts, no_reuse, false), - {ok, Port} = snmpm_config:system_info(port), - Domain = - case snmpm_config:system_info(domain) of - {ok, D} -> - D; - _ -> - snmpm_config:default_transport_domain() - end, - {ok, Sock} = do_open_port(Port, SndBuf, RecBuf, Domain, BindTo, NoReuse), - - %% Flow control -- - FilterOpts = get_opt(Opts, filter, []), - FilterMod = create_filter(FilterOpts), - ?vdebug("FilterMod: ~w", [FilterMod]), - - %% -- Audit trail log --- - {ok, ATL} = snmpm_config:system_info(audit_trail_log), - Log = do_init_log(ATL), - ?vdebug("Log: ~w", [Log]), - - %% -- Initiate counters --- - init_counters(), - - %% -- We are done --- - State = #state{server = Server, - note_store = NoteStore, - mpd_state = MpdState, - domain = Domain, - sock = Sock, - log = Log, - irb = IRB, - irgc = IrGcRef, - filter = FilterMod}, - ?vdebug("started", []), - {ok, State}. - - -%% Open port -do_open_port(Port, SendSz, RecvSz, Domain, BindTo, NoReuse) -> - ?vtrace("do_open_port -> entry with~n" - " Port: ~p~n" - " SendSz: ~p~n" - " RecvSz: ~p~n" - " Domain: ~p~n" - " BindTo: ~p~n" - " NoReuse: ~p", - [Port, SendSz, RecvSz, Domain, BindTo, NoReuse]), - IpOpts1 = bind_to(BindTo), - IpOpts2 = no_reuse(NoReuse), - IpOpts3 = recbuf(RecvSz), - IpOpts4 = sndbuf(SendSz), - IpOpts = - [binary, - snmp_conf:tdomain_to_family(Domain) | - IpOpts1 ++ IpOpts2 ++ IpOpts3 ++ IpOpts4], - OpenRes = - case init:get_argument(snmpm_fd) of - {ok, [[FdStr]]} -> - Fd = list_to_integer(FdStr), - gen_udp:open(0, [{fd, Fd}|IpOpts]); - error -> - gen_udp:open(Port, IpOpts) - end, - case OpenRes of - {error, _} = Error -> - throw(Error); - OK -> - OK - end. - -bind_to(true) -> - case snmpm_config:system_info(address) of - {ok, Addr} when is_list(Addr) -> - [{ip, list_to_tuple(Addr)}]; - {ok, Addr} -> - [{ip, Addr}]; - _ -> - [] - end; -bind_to(_) -> - []. - -no_reuse(false) -> - [{reuseaddr, true}]; -no_reuse(_) -> - []. - -recbuf(default) -> - []; -recbuf(Sz) -> - [{recbuf, Sz}]. - -sndbuf(default) -> - []; -sndbuf(Sz) -> - [{sndbuf, Sz}]. - - -create_filter(Opts) when is_list(Opts) -> - case get_opt(Opts, module, ?DEFAULT_FILTER_MODULE) of - ?DEFAULT_FILTER_MODULE = Mod -> - Mod; - Module -> - snmpm_network_interface_filter:verify(Module), - Module - end; -create_filter(BadOpts) -> - throw({error, {bad_filter_opts, BadOpts}}). - - -%% ---------------------------------------------------------------------- -%% Audit Trail Logger -%% ---------------------------------------------------------------------- - -%% Open log -do_init_log(false) -> - ?vtrace("do_init_log(false) -> entry", []), - undefined; -do_init_log(true) -> - ?vtrace("do_init_log(true) -> entry", []), - {ok, Type} = snmpm_config:system_info(audit_trail_log_type), - {ok, Dir} = snmpm_config:system_info(audit_trail_log_dir), - {ok, Size} = snmpm_config:system_info(audit_trail_log_size), - {ok, Repair} = snmpm_config:system_info(audit_trail_log_repair), - Name = ?audit_trail_log_name, - File = filename:absname(?audit_trail_log_file, Dir), - case snmpm_config:system_info(audit_trail_log_seqno) of - {ok, true} -> - Initial = ?ATL_SEQNO_INITIAL, - Max = ?ATL_SEQNO_MAX, - Module = snmpm_config, - Function = increment_counter, - Args = [atl_seqno, Initial, Max], - SeqNoGen = {Module, Function, Args}, - case snmp_log:create(Name, File, SeqNoGen, Size, Repair, true) of - {ok, Log} -> - ?vdebug("log created: ~w", [Log]), - {Name, Log, Type}; - {error, Reason} -> - throw({error, {failed_create_audit_log, Reason}}) - end; - _ -> - case snmp_log:create(Name, File, Size, Repair, true) of - {ok, Log} -> - ?vdebug("log created: ~w", [Log]), - {Name, Log, Type}; - {error, Reason} -> - throw({error, {failed_create_audit_log, Reason}}) - end - end. - - -%% ---------------------------------------------------------------------- - -%%-------------------------------------------------------------------- -%% Func: handle_call/3 -%% Returns: {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | (terminate/2 is called) -%% {stop, Reason, State} (terminate/2 is called) -%%-------------------------------------------------------------------- -handle_call({verbosity, Verbosity}, _From, State) -> - ?vlog("received verbosity request", []), - put(verbosity, Verbosity), - {reply, ok, State}; - -%% handle_call({system_info_updated, What}, _From, State) -> -%% ?vlog("received system_info_updated request with What = ~p", [What]), -%% {NewState, Reply} = handle_system_info_updated(State, What), -%% {reply, Reply, NewState}; - -handle_call(get_log_type, _From, State) -> - ?vlog("received get-log-type request", []), - Reply = (catch handle_get_log_type(State)), - {reply, Reply, State}; - -handle_call({set_log_type, NewType}, _From, State) -> - ?vlog("received set-log-type request with NewType = ~p", [NewType]), - {NewState, Reply} = (catch handle_set_log_type(State, NewType)), - {reply, Reply, NewState}; - -handle_call({note_store, Pid}, _From, State) -> - ?vlog("received new note_store: ~w", [Pid]), - {reply, ok, State#state{note_store = Pid}}; - -handle_call(stop, _From, State) -> - ?vlog("received stop request", []), - Reply = ok, - {stop, normal, Reply, State}; - -handle_call(info, _From, State) -> - ?vlog("received info request", []), - Reply = get_info(State), - {reply, Reply, State}; - -handle_call(Req, From, State) -> - warning_msg("received unknown request (from ~p): ~n~p", [Req, From]), - {reply, {error, {invalid_request, Req}}, State}. - - -%%-------------------------------------------------------------------- -%% Func: handle_cast/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%-------------------------------------------------------------------- -handle_cast({send_pdu, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo}, - State) -> - ?vlog("received send_pdu message with~n" - " Pdu: ~p~n" - " Vsn: ~p~n" - " MsgData: ~p~n" - " Domain: ~p~n" - " Addr: ~p", [Pdu, Vsn, MsgData, Domain, Addr]), - maybe_process_extra_info(ExtraInfo), - handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State), - {noreply, State}; - -handle_cast({inform_response, Ref, Domain, Addr}, State) -> - ?vlog("received inform_response message with~n" - " Ref: ~p~n" - " Domain: ~p~n" - " Addr: ~p", [Ref, Domain, Addr]), - handle_inform_response(Ref, Domain, Addr, State), - {noreply, State}; - -handle_cast(filter_reset, State) -> - ?vlog("received filter_reset message", []), - reset_counters(), - {noreply, State}; - -handle_cast(Msg, State) -> - warning_msg("received unknown message: ~n~p", [Msg]), - {noreply, State}. - - -%%-------------------------------------------------------------------- -%% Func: handle_info/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%-------------------------------------------------------------------- -handle_info( - {udp, Sock, Ip, Port, Bytes}, - #state{sock = Sock, domain = Domain} = State) -> - ?vlog("received ~w bytes from ~p:~p", [size(Bytes), Ip, Port]), - handle_udp(Domain, {Ip, Port}, Bytes, State), - {noreply, State}; - -handle_info(inform_response_gc, State) -> - ?vlog("received inform_response_gc message", []), - State2 = handle_inform_response_gc(State), - {noreply, State2}; - -handle_info({disk_log, _Node, Log, Info}, State) -> - ?vlog("received disk_log message: " - "~n Info: ~p", [Info]), - State2 = handle_disk_log(Log, Info, State), - {noreply, State2}; - -handle_info({'DOWN', _MRef, process, Pid, {net_if_worker, ExitStatus}}, - State) -> - ?vdebug("received DOWN message from net_if-worker: " - "~n ExitStatus: ~p", [ExitStatus]), - handle_worker_exit(Pid, ExitStatus), - {noreply, State}; - -handle_info(Info, State) -> - warning_msg("received unknown info: ~n~p", [Info]), - {noreply, State}. - - -%%-------------------------------------------------------------------- -%% Func: terminate/2 -%% Purpose: Shutdown the server -%% Returns: any (ignored by gen_server) -%%-------------------------------------------------------------------- -terminate(Reason, #state{log = Log, irgc = IrGcRef}) -> - ?vdebug("terminate: ~p", [Reason]), - irgc_stop(IrGcRef), - %% Close logs - do_close_log(Log), - ok. - - -do_close_log({Log, _Type}) -> - (catch snmp_log:sync(Log)), - (catch snmp_log:close(Log)), - ok; -do_close_log(_) -> - ok. - - -%%---------------------------------------------------------------------- -%% Func: code_change/3 -%% Purpose: Convert process state when code is changed -%% Returns: {ok, NewState} -%%---------------------------------------------------------------------- - -code_change(_Vsn, State, _Extra) -> - ?d("code_change -> entry with" - "~n Vsn: ~p" - "~n State: ~p" - "~n Extra: ~p", [_Vsn, State, _Extra]), - {ok, State}. - - -%%%------------------------------------------------------------------- -%%% Internal functions -%%%------------------------------------------------------------------- - -handle_udp(Domain, Addr, Bytes, State) -> - Verbosity = get(verbosity), - spawn_opt( - fun() -> - Log = worker_init(State, Verbosity), - Res = - (catch maybe_handle_recv_msg( - Domain, Addr, Bytes, - State#state{log = Log})), - worker_exit(udp, {Domain, Addr}, Res) - end, - [monitor]). - - -maybe_handle_recv_msg( - Domain, Addr, Bytes, - #state{filter = FilterMod, domain = ManagerDomain} = State) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), - case (catch FilterMod:accept_recv(Arg1, Arg2)) of - false -> - %% Drop the received packet - inc(netIfMsgInDrops), - ok; - _ -> - handle_recv_msg(Domain, Addr, Bytes, State) - end. - - -handle_recv_msg(Domain, Addr, Bytes, #state{server = Pid}) - when is_binary(Bytes) andalso (size(Bytes) =:= 0) -> - Pid ! {snmp_error, {empty_message, Domain, Addr}, Domain, Addr}, - ok; - -handle_recv_msg( - Domain, Addr, Bytes, - #state{server = Pid, - note_store = NoteStore, - mpd_state = MpdState, - log = Log} = State) -> - Logger = logger(Log, read, Domain, Addr), - case (catch snmpm_mpd:process_msg(Bytes, Domain, Addr, - MpdState, NoteStore, Logger)) of - - {ok, Vsn, Pdu, MS, ACM} -> - maybe_handle_recv_pdu(Domain, Addr, Vsn, Pdu, MS, ACM, - Logger, State); - - {discarded, Reason, Report} -> - ?vdebug("discarded: ~p", [Reason]), - ErrorInfo = {failed_processing_message, Reason}, - Pid ! {snmp_error, ErrorInfo, Domain, Addr}, - maybe_udp_send(Domain, Addr, Report, State), - ok; - {discarded, Reason} -> - ?vdebug("discarded: ~p", [Reason]), - ErrorInfo = {failed_processing_message, Reason}, - Pid ! {snmp_error, ErrorInfo, Domain, Addr}, - ok; - - Error -> - error_msg("processing of received message failed: " - "~n ~p", [Error]), - ok - end. - - -maybe_handle_recv_pdu( - Domain, Addr, Vsn, #pdu{type = Type} = Pdu, PduMS, ACM, Logger, - #state{filter = FilterMod, domain = ManagerDomain} = State) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), - case (catch FilterMod:accept_recv_pdu(Arg1, Arg2, Type)) of - false -> - inc(netIfPduInDrops), - ok; - _ -> - handle_recv_pdu( - Domain, Addr, Vsn, Pdu, PduMS, ACM, Logger, State) - end; -maybe_handle_recv_pdu( - Domain, Addr, Vsn, Trap, PduMS, ACM, Logger, - #state{filter = FilterMod, domain = ManagerDomain} = State) - when is_record(Trap, trappdu) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), - case (catch FilterMod:accept_recv_pdu(Arg1, Arg2, trappdu)) of - false -> - inc(netIfPduInDrops), - ok; - _ -> - handle_recv_pdu( - Domain, Addr, Vsn, Trap, PduMS, ACM, Logger, State) - end; -maybe_handle_recv_pdu( - Domain, Addr, Vsn, Pdu, PduMS, ACM, Logger, State) -> - handle_recv_pdu(Domain, Addr, Vsn, Pdu, PduMS, ACM, Logger, State). - - -handle_recv_pdu( - Domain, Addr, Vsn, - #pdu{type = 'inform-request'} = Pdu, _PduMS, ACM, Logger, - #state{server = Pid, irb = IRB} = State) -> - handle_inform_request( - IRB, Pid, Vsn, Pdu, ACM, Domain, Addr, Logger, State); -handle_recv_pdu( - Domain, Addr, _Vsn, - #pdu{type = report} = Pdu, _PduMS, ok, _Logger, - #state{server = Pid} = _State) -> - ?vtrace("received report - ok", []), - Pid ! {snmp_report, {ok, Pdu}, Domain, Addr}, - ok; -handle_recv_pdu( - Domain, Addr, _Vsn, - #pdu{type = report} = Pdu, _PduMS, {error, ReqId, Reason}, _Logger, - #state{server = Pid} = _State) -> - ?vtrace("received report - error", []), - Pid ! {snmp_report, {error, ReqId, Reason, Pdu}, Domain, Addr}, - ok; -handle_recv_pdu( - Domain, Addr, _Vsn, - #pdu{type = 'snmpv2-trap'} = Pdu, _PduMS, _ACM, _Logger, - #state{server = Pid} = _State) -> - ?vtrace("received snmpv2-trap", []), - Pid ! {snmp_trap, Pdu, Domain, Addr}, - ok; -handle_recv_pdu( - Domain, Addr, _Vsn, Trap, _PduMS, _ACM, _Logger, - #state{server = Pid} = _State) when is_record(Trap, trappdu) -> - ?vtrace("received trappdu", []), - Pid ! {snmp_trap, Trap, Domain, Addr}, - ok; -handle_recv_pdu( - Domain, Addr, _Vsn, Pdu, _PduMS, _ACM, _Logger, - #state{server = Pid} = _State) when is_record(Pdu, pdu) -> - ?vtrace("received pdu", []), - Pid ! {snmp_pdu, Pdu, Domain, Addr}, - ok; -handle_recv_pdu( - _Domain, _Addr, _Vsn, Pdu, _PduMS, ACM, _Logger, _State) -> - ?vlog("received unexpected pdu: " - "~n Pdu: ~p" - "~n ACM: ~p", [Pdu, ACM]), - ok. - - -handle_inform_request( - auto, Pid, Vsn, Pdu, ACM, Domain, Addr, Logger, State) -> - ?vtrace("received inform-request (true)", []), - Pid ! {snmp_inform, ignore, Pdu, Domain, Addr}, - RePdu = make_response_pdu(Pdu), - maybe_send_inform_response(RePdu, Vsn, ACM, Domain, Addr, Logger, State); -handle_inform_request( - {user, To}, Pid, Vsn, #pdu{request_id = ReqId} = Pdu, - ACM, Domain, Addr, _Logger, _State) -> - ?vtrace("received inform-request (false)", []), - - Pid ! {snmp_inform, ReqId, Pdu, Domain, Addr}, - - %% Before we go any further, we need to check that we have not - %% already received this message (possible resend). - - Key = {ReqId, Domain, Addr}, - case ets:lookup(snmpm_inform_request_table, Key) of - [_] -> - %% OK, we already know about this. We assume this - %% is a resend. Either the agent is really eager or - %% the user has not answered yet. Bad user! - ok; - [] -> - RePdu = make_response_pdu(Pdu), - Expire = t() + To, - Rec = {Key, Expire, {Vsn, ACM, RePdu}}, - ets:insert(snmpm_inform_request_table, Rec) - end, - ok. - -handle_inform_response(Ref, Domain, Addr, State) -> - Verbosity = get(verbosity), - spawn_opt( - fun() -> - Log = worker_init(State, Verbosity), - Res = (catch do_handle_inform_response( - Ref, Domain, Addr, State#state{log = Log})), - worker_exit(inform_response, {Domain, Addr}, Res) - end, - [monitor]). - - - -do_handle_inform_response(Ref, Domain, Addr, State) -> - Key = {Ref, Domain, Addr}, - case ets:lookup(snmpm_inform_request_table, Key) of - [{Key, _, {Vsn, ACM, RePdu}}] -> - Logger = logger(State#state.log, read, Domain, Addr), - ets:delete(snmpm_inform_request_table, Key), - maybe_send_inform_response( - RePdu, Vsn, ACM, Domain, Addr, Logger, State); - [] -> - %% Already acknowledged, or the user was to slow to reply... - ok - end, - ok. - -maybe_send_inform_response( - RePdu, Vsn, ACM, Domain, Addr, Logger, - #state{server = Pid, - sock = Sock, - domain = ManagerDomain, - filter = FilterMod}) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), - case (catch FilterMod:accept_send_pdu( - Arg1, Arg2, pdu_type_of(RePdu))) - of - false -> - inc(netIfPduOutDrops), - ok; - _ -> - case snmpm_mpd:generate_response_msg(Vsn, RePdu, ACM, Logger) of - {ok, Msg} -> - maybe_udp_send( - Domain, Addr, Msg, Sock, FilterMod, ManagerDomain); - {discarded, Reason} -> - ?vlog("failed generating response message:" - "~n Reason: ~p", [Reason]), - ReqId = RePdu#pdu.request_id, - ErrorInfo = {failed_generating_response, {RePdu, Reason}}, - Pid ! {snmp_error, ReqId, ErrorInfo, Domain, Addr}, - ok - end - end. - -handle_inform_response_gc(#state{irb = IRB} = State) -> - ets:safe_fixtable(snmpm_inform_request_table, true), - do_irgc(ets:first(snmpm_inform_request_table), t()), - ets:safe_fixtable(snmpm_inform_request_table, false), - State#state{irgc = irgc_start(IRB)}. - -%% We are deleting at the same time as we are traversing the table!!! -do_irgc('$end_of_table', _) -> - ok; -do_irgc(Key, Now) -> - Next = ets:next(snmpm_inform_request_table, Key), - case ets:lookup(snmpm_inform_request_table, Key) of - [{Key, BestBefore, _}] when BestBefore < Now -> - ets:delete(snmpm_inform_request_table, Key); - _ -> - ok - end, - do_irgc(Next, Now). - -irgc_start(auto) -> - undefined; -irgc_start(_) -> - erlang:send_after(?IRGC_TIMEOUT, self(), inform_response_gc). - -irgc_stop(undefined) -> - ok; -irgc_stop(Ref) -> - (catch erlang:cancel_timer(Ref)). - - -handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State) -> - Verbosity = get(verbosity), - spawn_opt( - fun() -> - Log = worker_init(State, Verbosity), - Res = (catch maybe_handle_send_pdu( - Pdu, Vsn, MsgData, - Domain, Addr, - State#state{log = Log})), - worker_exit(send_pdu, {Domain, Addr}, Res) - end, - [monitor]). - -maybe_handle_send_pdu( - Pdu, Vsn, MsgData, Domain, Addr, - #state{filter = FilterMod, domain = ManagerDomain} = State) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), - case (catch FilterMod:accept_send_pdu(Arg1, Arg2, pdu_type_of(Pdu))) of - false -> - inc(netIfPduOutDrops), - ok; - _ -> - do_handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State) - end. - -do_handle_send_pdu( - Pdu, Vsn, MsgData, Domain, Addr, - #state{server = Pid, - note_store = NoteStore, - log = Log} = State) -> - Logger = logger(Log, write, Domain, Addr), - case (catch snmpm_mpd:generate_msg( - Vsn, NoteStore, Pdu, MsgData, Logger)) of - {ok, Msg} -> - ?vtrace("do_handle_send_pdu -> message generated", []), - maybe_udp_send(Domain, Addr, Msg, State); - {discarded, Reason} -> - ?vlog("PDU not sent: " - "~n PDU: ~p" - "~n Reason: ~p", [Pdu, Reason]), - Pid ! {snmp_error, Pdu, Reason}, - ok - end. - -maybe_udp_send( - Domain, Addr, Msg, - #state{sock = Sock, filter = FilterMod, domain = ManagerDomain}) -> - maybe_udp_send(Domain, Addr, Msg, Sock, FilterMod, ManagerDomain). - -maybe_udp_send(Domain, Addr, Msg, Sock, FilterMod, ManagerDomain) -> - {Arg1, Arg2} = fix_filter_address(ManagerDomain, {Domain, Addr}), - case (catch FilterMod:accept_send(Arg1, Arg2)) of - false -> - inc(netIfMsgOutDrops), - ok; - _ -> - %% XXX There should be some kind of lookup of socket - %% from transport domain here - {Ip, Port} = Addr, - udp_send(Sock, Ip, Port, Msg) - end. - - -udp_send(Sock, Ip, Port, Msg) -> - case (catch gen_udp:send(Sock, Ip, Port, Msg)) of - ok -> - ?vdebug("sent ~w bytes to ~w:~w [~w]", - [sz(Msg), Ip, Port, Sock]), - ok; - {error, Reason} -> - error_msg("failed sending message to ~p:~p: " - "~n ~p", [Ip, Port, Reason]), - ok; - Error -> - error_msg("failed sending message to ~p:~p: " - "~n ~p", [Ip, Port, Error]), - ok - end. - -sz(B) when is_binary(B) -> - size(B); -sz(L) when is_list(L) -> - length(L); -sz(_) -> - undefined. - - -handle_disk_log(_Log, {wrap, NoLostItems}, State) -> - ?vlog("Audit Trail Log - wrapped: ~w previously logged items where lost", - [NoLostItems]), - State; -handle_disk_log(_Log, {truncated, NoLostItems}, State) -> - ?vlog("Audit Trail Log - truncated: ~w items where lost when truncating", - [NoLostItems]), - State; -handle_disk_log(_Log, full, State) -> - error_msg("Failed to write to Audit Trail Log (full)", []), - State; -handle_disk_log(_Log, {error_status, ok}, State) -> - State; -handle_disk_log(_Log, {error_status, {error, Reason}}, State) -> - error_msg("Error status received from Audit Trail Log: " - "~n~p", [Reason]), - State; -handle_disk_log(_Log, _Info, State) -> - State. - - -%% mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) -> -%% ScopedPDU = #scopedPdu{contextEngineID = "", -%% contextName = "", -%% data = Pdu}, -%% Bytes = snmp_pdus:enc_scoped_pdu(ScopedPDU), -%% MsgID = get(msg_id), -%% put(msg_id,MsgID+1), -%% UsmSecParams = -%% #usmSecurityParameters{msgAuthoritativeEngineID = "", -%% msgAuthoritativeEngineBoots = 0, -%% msgAuthoritativeEngineTime = 0, -%% msgUserName = UserName, -%% msgPrivacyParameters = "", -%% msgAuthenticationParameters = ""}, -%% SecBytes = snmp_pdus:enc_usm_security_parameters(UsmSecParams), -%% PduType = Pdu#pdu.type, -%% Hdr = #v3_hdr{msgID = MsgID, -%% msgMaxSize = 1000, -%% msgFlags = snmp_misc:mk_msg_flags(PduType, 0), -%% msgSecurityModel = ?SEC_USM, -%% msgSecurityParameters = SecBytes}, -%% Msg = #message{version = 'version-3', vsn_hdr = Hdr, data = Bytes}, -%% case (catch snmp_pdus:enc_message_only(Msg)) of -%% {'EXIT', Reason} -> -%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), -%% error; -%% L when list(L) -> -%% {Msg, L} -%% end; -%% mk_discovery_msg(Version, Pdu, {Com, _, _, _, _}, UserName) -> -%% Msg = #message{version = Version, vsn_hdr = Com, data = Pdu}, -%% case catch snmp_pdus:enc_message(Msg) of -%% {'EXIT', Reason} -> -%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), -%% error; -%% L when list(L) -> -%% {Msg, L} -%% end. - - -%% mk_msg('version-3', Pdu, {Context, User, EngineID, CtxEngineId, SecLevel}, -%% MsgData) -> -%% %% Code copied from snmp_mpd.erl -%% {MsgId, SecName, SecData} = -%% if -%% tuple(MsgData), Pdu#pdu.type == 'get-response' -> -%% MsgData; -%% true -> -%% Md = get(msg_id), -%% put(msg_id, Md + 1), -%% {Md, User, []} -%% end, -%% ScopedPDU = #scopedPdu{contextEngineID = CtxEngineId, -%% contextName = Context, -%% data = Pdu}, -%% ScopedPDUBytes = snmp_pdus:enc_scoped_pdu(ScopedPDU), - -%% PduType = Pdu#pdu.type, -%% V3Hdr = #v3_hdr{msgID = MsgId, -%% msgMaxSize = 1000, -%% msgFlags = snmp_misc:mk_msg_flags(PduType, SecLevel), -%% msgSecurityModel = ?SEC_USM}, -%% Message = #message{version = 'version-3', vsn_hdr = V3Hdr, -%% data = ScopedPDUBytes}, -%% SecEngineID = case PduType of -%% 'get-response' -> snmp_framework_mib:get_engine_id(); -%% _ -> EngineID -%% end, -%% case catch snmp_usm:generate_outgoing_msg(Message, SecEngineID, -%% SecName, SecData, SecLevel) of -%% {'EXIT', Reason} -> -%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), -%% error; -%% {error, Reason} -> -%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), -%% error; -%% Packet -> -%% Packet -%% end; -%% mk_msg(Version, Pdu, {Com, _User, _EngineID, _Ctx, _SecLevel}, _SecData) -> -%% Msg = #message{version = Version, vsn_hdr = Com, data = Pdu}, -%% case catch snmp_pdus:enc_message(Msg) of -%% {'EXIT', Reason} -> -%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), -%% error; -%% B when list(B) -> -%% B -%% end. - - -%% handle_system_info_updated(#state{log = {Log, _OldType}} = State, -%% audit_trail_log_type = _What) -> -%% %% Just to make sure, check that ATL is actually enabled -%% case snmpm_config:system_info(audit_trail_log) of -%% {ok, true} -> -%% {ok, Type} = snmpm_config:system_info(audit_trail_log_type), -%% NewState = State#state{log = {Log, Type}}, -%% {NewState, ok}; -%% _ -> -%% {State, {error, {adt_not_enabled}}} -%% end; -%% handle_system_info_updated(_State, _What) -> -%% ok. - -handle_get_log_type(#state{log = {_Log, Value}} = State) -> - %% Just to make sure, check that ATL is actually enabled - case snmpm_config:system_info(audit_trail_log) of - {ok, true} -> - Type = - case {lists:member(read, Value), lists:member(write, Value)} of - {true, true} -> - read_write; - {true, false} -> - read; - {false, true} -> - write; - {false, false} -> - throw({State, {error, {bad_atl_type, Value}}}) - end, - {ok, Type}; - _ -> - {error, not_enabled} - end; -handle_get_log_type(_State) -> - {error, not_enabled}. - -handle_set_log_type(#state{log = {Log, OldValue}} = State, NewType) -> - %% Just to make sure, check that ATL is actually enabled - case snmpm_config:system_info(audit_trail_log) of - {ok, true} -> - NewValue = - case NewType of - read -> - [read]; - write -> - [write]; - read_write -> - [read,write]; - _ -> - throw({State, {error, {bad_atl_type, NewType}}}) - end, - NewState = State#state{log = {Log, NewValue}}, - OldType = - case {lists:member(read, OldValue), - lists:member(write, OldValue)} of - {true, true} -> - read_write; - {true, false} -> - read; - {false, true} -> - write; - {false, false} -> - throw({State, {error, {bad_atl_type, OldValue}}}) - end, - {NewState, {ok, OldType}}; - _ -> - {State, {error, not_enabled}} - end; -handle_set_log_type(State, _NewType) -> - {State, {error, not_enabled}}. - - -%% ------------------------------------------------------------------- - -worker_init(#state{log = undefined = Log}, Verbosity) -> - worker_init2(Log, Verbosity); -worker_init(#state{log = {Name, Log, Type}}, Verbosity) -> - case snmp_log:open(Name, Log) of - {ok, NewLog} -> - worker_init2({Name, NewLog, Type}, Verbosity); - {error, Reason} -> - warning_msg("NetIf worker ~p failed opening ATL: " - "~n ~p", [self(), Reason]), - NewLog = undefined, - worker_init2({Name, NewLog, Type}, Verbosity) - end; -worker_init(State, Verbosity) -> - ?vinfo("worker_init -> entry with invalid data: " - "~n State: ~p" - "~n Verbosity: ~p", [State, Verbosity]), - exit({worker_init, State, Verbosity}). - -worker_init2(Log, Verbosity) -> - put(sname, mnifw), - put(verbosity, Verbosity), - Log. - - -worker_exit(Tag, Info, Result) -> - exit({net_if_worker, {Tag, Info, Result}}). - -handle_worker_exit(_, {_, _, ok}) -> - ok; -handle_worker_exit(Pid, {udp, {Domain, Addr}, ExitStatus}) -> - warning_msg( - "Worker process (~p) terminated " - "while processing (incomming) message from %s:~n" - "~p", [Pid, snmp_conf:mk_addr_string({Domain, Addr}), ExitStatus]), - ok; -handle_worker_exit(Pid, {send_pdu, {Domain, Addr}, ExitStatus}) -> - warning_msg( - "Worker process (~p) terminated " - "while processing (outgoing) pdu for %s:~n" - "~p", [Pid, snmp_conf:mk_addr_string({Domain, Addr}), ExitStatus]), - ok; -handle_worker_exit(Pid, {inform_response, {Domain, Addr}, ExitStatus}) -> - warning_msg( - "Worker process (~p) terminated " - "while processing (outgoing) inform response for %s:~n" - "~p", [Pid, snmp_conf:mk_addr_string({Domain, Addr}), ExitStatus]), - ok; -handle_worker_exit(_, _) -> - ok. - - -%% If the manager uses legacy snmpUDPDomain e.g has not set -%% {domain, _}, then make sure snmpm_network_interface_filter -%% gets legacy arguments to not break backwards compatibility. -%% -fix_filter_address(snmpUDPDomain, {Domain, Addr}) - when Domain =:= snmpUDPDomain; - Domain =:= transportDomainUdpIpv4 -> - Addr; -fix_filter_address(_ManagerDomain, {Domain, _} = Address) - when is_atom(Domain) -> - Address; -fix_filter_address(snmpUDPDomain, {_, Port} = Addr) - when is_integer(Port) -> - Addr. - -address(Domain, Addr) when is_atom(Domain) -> - {Domain, Addr}; -address(Ip, Port) when is_integer(Port) -> - {snmpm_config:default_transport_domain(), {Ip, Port}}. - -%% ------------------------------------------------------------------- - -make_response_pdu(#pdu{request_id = ReqId, varbinds = Vbs}) -> - #pdu{type = 'get-response', - request_id = ReqId, - error_status = noError, - error_index = 0, - varbinds = Vbs}. - - -%% ---------------------------------------------------------------- - -pdu_type_of(#pdu{type = Type}) -> - Type; -pdu_type_of(TrapPdu) when is_record(TrapPdu, trappdu) -> - trap. - - -%% ------------------------------------------------------------------- - -%% At this point this function is used during testing -maybe_process_extra_info(?DEFAULT_EXTRA_INFO) -> - ok; -maybe_process_extra_info({?SNMPM_EXTRA_INFO_TAG, Fun}) - when is_function(Fun, 0) -> - (catch Fun()), - ok; -maybe_process_extra_info(_ExtraInfo) -> - ok. - - -%% ------------------------------------------------------------------- - -t() -> - {A,B,C} = erlang:now(), - A*1000000000+B*1000+(C div 1000). - - -%% ------------------------------------------------------------------- - -logger(undefined, _Type, _Domain, _Addr) -> - fun(_) -> - ok - end; -logger({_Name, Log, Types}, Type, Domain, Addr) -> - case lists:member(Type, Types) of - true -> - AddrString = - iolist_to_binary(snmp_conf:mk_addr_string({Domain, Addr})), - fun(Msg) -> - snmp_log:log(Log, Msg, Domain, AddrString) - end; - false -> - fun(_) -> - ok - end - end. - - -%% ------------------------------------------------------------------- - -%% info_msg(F, A) -> -%% ?snmpm_info("NET-IF server: " ++ F, A). - -warning_msg(F, A) -> - ?snmpm_warning("NET-IF server: " ++ F, A). - -error_msg(F, A) -> - ?snmpm_error("NET-IF server: " ++ F, A). - - - -%%%------------------------------------------------------------------- - -% get_opt(Key, Opts) -> -% ?vtrace("get option ~w", [Key]), -% snmp_misc:get_option(Key, Opts). - -get_opt(Opts, Key, Def) -> - ?vtrace("get option ~w with default ~p", [Key, Def]), - snmp_misc:get_option(Key, Opts, Def). - - -%% ------------------------------------------------------------------- - -get_info(#state{sock = Id}) -> - ProcSize = proc_mem(self()), - PortInfo = get_port_info(Id), - [{process_memory, ProcSize}, {port_info, PortInfo}]. - -proc_mem(P) when is_pid(P) -> - case (catch erlang:process_info(P, memory)) of - {memory, Sz} when is_integer(Sz) -> - Sz; - _ -> - undefined - end. -%% proc_mem(_) -> -%% undefined. - - -get_port_info(Id) -> - PortInfo = - case (catch erlang:port_info(Id)) of - PI when is_list(PI) -> - [{port_info, PI}]; - _ -> - [] - end, - PortStatus = - case (catch prim_inet:getstatus(Id)) of - {ok, PS} -> - [{port_status, PS}]; - _ -> - [] - end, - PortAct = - case (catch inet:getopts(Id, [active])) of - {ok, PA} -> - [{port_act, PA}]; - _ -> - [] - end, - PortStats = - case (catch inet:getstat(Id)) of - {ok, Stat} -> - [{port_stats, Stat}]; - _ -> - [] - end, - IfList = - case (catch inet:getif(Id)) of - {ok, IFs} -> - [{interfaces, IFs}]; - _ -> - [] - end, - BufSz = - case (catch inet:getopts(Id, [recbuf, sndbuf, buffer])) of - {ok, Sz} -> - [{buffer_size, Sz}]; - _ -> - [] - end, - [{socket, Id}] ++ - IfList ++ - PortStats ++ - PortInfo ++ - PortStatus ++ - PortAct ++ - BufSz. - - -%%----------------------------------------------------------------- -%% Counter functions -%%----------------------------------------------------------------- -init_counters() -> - F = fun(Counter) -> maybe_create_counter(Counter) end, - lists:map(F, counters()). - -reset_counters() -> - F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end, - lists:map(F, counters()). - -maybe_create_counter(Counter) -> - snmpm_config:maybe_cre_stats_counter(Counter, 0). - -counters() -> - [ - netIfMsgOutDrops, - netIfMsgInDrops, - netIfPduOutDrops, - netIfPduInDrops - ]. - -inc(Name) -> inc(Name, 1). -inc(Name, N) -> snmpm_config:incr_stats_counter(Name, N). - -%% get_counters() -> -%% Counters = counters(), -%% get_counters(Counters, []). - -%% get_counters([], Acc) -> -%% lists:reverse(Acc); -%% get_counters([Counter|Counters], Acc) -> -%% case snmpm_config:get_stats_counter(Counter) of -%% {ok, CounterVal} -> -%% get_counters(Counters, [{Counter, CounterVal}|Acc]); -%% _ -> -%% get_counters(Counters, Acc) -%% end. - - -%% ---------------------------------------------------------------- - -call(Pid, Req) -> - call(Pid, Req, infinity). - -call(Pid, Req, Timeout) -> - gen_server:call(Pid, Req, Timeout). - -cast(Pid, Msg) -> - gen_server:cast(Pid, Msg). +-define(snmpm_net_if_mt, true). +-module(snmpm_net_if_mt). +-include("snmpm_net_if.erl"). diff --git a/lib/snmp/src/manager/snmpm_server.erl b/lib/snmp/src/manager/snmpm_server.erl index ece5dad082..a75122d0bb 100644 --- a/lib/snmp/src/manager/snmpm_server.erl +++ b/lib/snmp/src/manager/snmpm_server.erl @@ -2079,7 +2079,16 @@ do_handle_agent(DefUserId, DefMod, SnmpInfo, DefData, State) -> ?vdebug("do_handle_agent -> entry when" "~n DefUserId: ~p", [DefUserId]), - try DefMod:handle_agent(Domain, Addr, Type, SnmpInfo, DefData) of + {Domain_or_Ip, Addr_or_Port} = + case Domain of + snmpUDPDomain -> + Addr; + _ -> + {Domain, Addr} + end, + try DefMod:handle_agent( + Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData) + of {register, UserId2, TargetName, Config} -> ?vtrace("do_handle_agent -> register: " "~n UserId2: ~p" diff --git a/lib/snmp/src/misc/snmp_conf.erl b/lib/snmp/src/misc/snmp_conf.erl index f4483995cb..153c8070c2 100644 --- a/lib/snmp/src/misc/snmp_conf.erl +++ b/lib/snmp/src/misc/snmp_conf.erl @@ -749,8 +749,6 @@ which_domain({A0, A1, A2, A3, A4, A5, A6, A7}) %% --------- -mk_addr_string({_IP, Port} = Addr) when is_integer(Port) -> - mk_addr_string({snmpUDPDomain, Addr}); mk_addr_string({Domain, Addr}) when is_atom(Domain) -> %% XXX There is only code for IP domains here case check_address_ip(Domain, Addr) of @@ -768,7 +766,11 @@ mk_addr_string({Domain, Addr}) when is_atom(Domain) -> mk_addr_string_ntoa(Domain, Addr); IP -> mk_addr_string_ntoa(Domain, IP) - end. + end; +mk_addr_string({_IP, Port} = Addr) when is_integer(Port) -> + mk_addr_string({snmpUDPDomain, Addr}); +mk_addr_string(Strange) -> + lists:flatten(io_lib:format("~w", [Strange])). mk_addr_string_ntoa({_, _, _, _} = IP) -> diff --git a/lib/snmp/src/misc/snmp_config.erl b/lib/snmp/src/misc/snmp_config.erl index 9e9865f3b5..17dfcd70b4 100644 --- a/lib/snmp/src/misc/snmp_config.erl +++ b/lib/snmp/src/misc/snmp_config.erl @@ -47,8 +47,8 @@ write_agent_snmp_vacm_conf/3, write_manager_snmp_files/8, - write_manager_snmp_conf/5, - write_manager_snmp_users_conf/2, + write_manager_snmp_conf/4, write_manager_snmp_conf/5, + write_manager_snmp_users_conf/2, write_manager_snmp_agents_conf/2, write_manager_snmp_usm_conf/2 @@ -1676,7 +1676,9 @@ write_agent_snmp_conf(Dir, AgentIP, AgentUDP, EngineID, MMS) {intAgentIpAddress, AgentIP}, {snmpEngineID, EngineID}, {snmpEngineMaxMessageSize, MMS}], - do_write_agent_snmp_conf(Dir, Conf). + do_write_agent_snmp_conf(Dir, Conf); +write_agent_snmp_conf(_Dir, Domain, AgentAddr, _EngineID, _MMS) -> + error({bad_address, {Domain, AgentAddr}}). do_write_agent_snmp_conf(Dir, Conf) -> Comment = @@ -2116,7 +2118,24 @@ write_manager_snmp_files(Dir, IP, Port, MMS, EngineID, %% ------ manager.conf ------ %% -write_manager_snmp_conf(Dir, IP, Port, MMS, EngineID) -> +write_manager_snmp_conf(Dir, Transports, MMS, EngineID) -> + Comment = +"%% This file defines the Manager local configuration info\n" +"%% Each row is a 2-tuple:\n" +"%% {Variable, Value}.\n" +"%% For example\n" +"%% {transports, [{transportDomainUdpIpv4, {{127,42,17,5}, 5000}}]}.\n" +"%% {engine_id, \"managerEngine\"}.\n" +"%% {max_message_size, 484}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + Conf = + [{transports, Transports}, + {engine_id, EngineID}, + {max_message_size, MMS}], + write_manager_config(Dir, Hdr, Conf). + +write_manager_snmp_conf(Dir, Domain_or_IP, Addr_or_Port, MMS, EngineID) -> Comment = "%% This file defines the Manager local configuration info\n" "%% Each row is a 2-tuple:\n" @@ -2129,15 +2148,16 @@ write_manager_snmp_conf(Dir, IP, Port, MMS, EngineID) -> "%%\n\n", Hdr = header() ++ Comment, Conf = - case Port of - {Addr, P} when is_integer(P), is_atom(IP) -> - Domain = IP, - [{domain, Domain}, - {port, P}, - {address, Addr}]; - _ when is_integer(Port) -> - [{port, Port}, - {address, IP}] + case Addr_or_Port of + {IP, Port} when is_integer(Port), is_atom(Domain_or_IP) -> + [{domain, Domain_or_IP}, + {port, Port}, + {address, IP}]; + _ when is_integer(Addr_or_Port) -> + [{port, Addr_or_Port}, + {address, Domain_or_IP}]; + _ -> + error({bad_address, {Domain_or_IP, Addr_or_Port}}) end ++ [{engine_id, EngineID}, {max_message_size, MMS}], diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl index 9a9258aa91..b4770ad0a9 100644 --- a/lib/snmp/test/snmp_agent_test.erl +++ b/lib/snmp/test/snmp_agent_test.erl @@ -532,6 +532,7 @@ groups() -> {v3_inform, [], v3_inform_cases()}, {v3_security, [], v3_security_cases()}, {standard_mibs, [], standard_mibs_cases()}, + {standard_mibs_ipv6, [], standard_mibs_cases_ipv6()}, {standard_mibs_2, [], standard_mibs2_cases()}, {standard_mibs_3, [], standard_mibs3_cases()}, {reported_bugs, [], reported_bugs_cases()}, @@ -651,11 +652,18 @@ init_per_group(GroupName, Config) -> init_per_group_ipv6(GroupName, Config, Init) -> case ct:require(ipv6_hosts) of ok -> - Init( - snmp_test_lib:init_group_top_dir( - GroupName, - [{ipfamily, inet6}, - {ip, ?LOCALHOST(inet6)} | lists:keydelete(ip, 1, Config)])); + case gen_udp:open(0, [inet6]) of + {ok, S} -> + ok = gen_udp:close(S), + Init( + snmp_test_lib:init_group_top_dir( + GroupName, + [{ipfamily, inet6}, + {ip, ?LOCALHOST(inet6)} + | lists:keydelete(ip, 1, Config)])); + {error, _} -> + {skip, "Host seems to not support IPv6"} + end; _ -> {skip, "Host does not support IPV6"} end. @@ -1714,7 +1722,7 @@ v1_cases_ipv6() -> next_across_sa, undo, %% {group, reported_bugs}, - {group, standard_mibs}, % snmp_standard_mib still failing, sends v1 trap + {group, standard_mibs_ipv6}, sparse_table, %% cnt_64, % sends v1 trap opaque @@ -4836,6 +4844,15 @@ standard_mibs_cases() -> snmp_view_based_acm_mib ]. +standard_mibs_cases_ipv6() -> + [ + %% snmp_standard_mib, % Sending v1 traps does not work over IPv6 + snmp_community_mib, + snmp_framework_mib, + snmp_target_mib, + snmp_notification_mib, + snmp_view_based_acm_mib + ]. %%----------------------------------------------------------------- %% For this test, the agent is configured for v1. diff --git a/lib/snmp/test/snmp_manager_config_test.erl b/lib/snmp/test/snmp_manager_config_test.erl index 2f5c68d14d..f37e957dae 100644 --- a/lib/snmp/test/snmp_manager_config_test.erl +++ b/lib/snmp/test/snmp_manager_config_test.erl @@ -1048,7 +1048,7 @@ start_with_invalid_agents_conf_file1(Conf) when is_list(Conf) -> case config_start(Opts) of {error, Reason51} -> p("start failed (as expected): ~p", [Reason51]), - ?line {failed_check, _, _, _, {bad_address, _}} = Reason51, + ?line {failed_check, _, _, _, {bad_domain, _}} = Reason51, await_config_not_running(); OK_51 -> exit({error, {unexpected_success, "51", OK_51}}) diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl index 78352e59cf..fa90872172 100644 --- a/lib/snmp/test/snmp_manager_test.erl +++ b/lib/snmp/test/snmp_manager_test.erl @@ -584,17 +584,30 @@ init_per_group(event_tests_mt = GroupName, Config) -> init_per_group(ipv6_mt = GroupName, Config) -> case ct:require(ipv6_hosts) of ok -> - ipv6_init( - snmp_test_lib:init_group_top_dir( - GroupName, - [{manager_net_if_module, snmpm_net_if_mt} | Config])); + case gen_udp:open(0, [inet6]) of + {ok, S} -> + ok = gen_udp:close(S), + ipv6_init( + snmp_test_lib:init_group_top_dir( + GroupName, + [{manager_net_if_module, snmpm_net_if_mt} + | Config])); + {error, _} -> + {skip, "Host seems to not support IPv6"} + end; _ -> {skip, "Host does not support IPV6"} end; init_per_group(ipv6 = GroupName, Config) -> case ct:require(ipv6_hosts) of ok -> - ipv6_init(snmp_test_lib:init_group_top_dir(GroupName, Config)); + case gen_udp:open(0, [inet6]) of + {ok, S} -> + ok = gen_udp:close(S), + ipv6_init(snmp_test_lib:init_group_top_dir(GroupName, Config)); + {error, _} -> + {skip, "Host seems to not support IPv6"} + end; _ -> {skip, "Host does not support IPV6"} end; diff --git a/lib/snmp/test/snmp_manager_user.erl b/lib/snmp/test/snmp_manager_user.erl index ddbe156130..46c2b316be 100644 --- a/lib/snmp/test/snmp_manager_user.erl +++ b/lib/snmp/test/snmp_manager_user.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2013. All Rights Reserved. +%% Copyright Ericsson AB 2005-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -109,7 +109,18 @@ start_link(Parent, Id) -> proc_lib:start_link(?MODULE, main, [true, Parent, self(), Id]). stop() -> - cast(stop). + MRef = erlang:monitor(process, ?SERVER), + cast(stop), + receive {'DOWN', MRef, _, _, Info} -> + case Info of + noproc -> + ok; + noconnection -> + ok; + normal -> + ok + end + end. info() -> call(info). diff --git a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl index 6e9c57bce9..2f96493ac5 100644 --- a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl +++ b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl @@ -19,6 +19,7 @@ %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test suite uses the following external programs: %% snmpget From packet 'snmp' (in Ubuntu 12.04) +%% snmpd From packet 'snmpd' (in Ubuntu 12.04) %% snmptrapd From packet 'snmpd' (in Ubuntu 12.04) %% They originate from the Net-SNMP applications, see: %% http://net-snmp.sourceforge.net/ @@ -33,6 +34,7 @@ -include_lib("snmp/include/STANDARD-MIB.hrl"). -define(AGENT_ENGINE_ID, "ErlangSnmpAgent"). +-define(MANAGER_ENGINE_ID, "ErlangSnmpManager"). -define(AGENT_PORT, 4000). -define(MANAGER_PORT, 8989). -define(DEFAULT_MAX_MESSAGE_SIZE, 484). @@ -56,22 +58,32 @@ all() -> groups() -> [{ipv4, [], - [{group, get}, - {group, inform} + [{group, snmpget}, + {group, snmptrapd}, + {group, snmpd_mt}, + {group, snmpd} ]}, {ipv6, [], - [{group, get}, - {group, inform} + [{group, snmpget}, + {group, snmptrapd}, + {group, snmpd_mt}, + {group, snmpd} ]}, {ipv4_ipv6, [], - [{group, get}, - {group, inform} + [{group, snmpget}, + {group, snmptrapd}, + {group, snmpd_mt}, + {group, snmpd} ]}, %% - {get, [], + {snmpget, [], [erlang_agent_netsnmp_get]}, - {inform, [], - [erlang_agent_netsnmp_inform]} + {snmptrapd, [], + [erlang_agent_netsnmp_inform]}, + {snmpd_mt, [], + [erlang_manager_netsnmp_get]}, + {snmpd, [], + [erlang_manager_netsnmp_get]} ]. init_per_suite(Config) -> @@ -87,12 +99,22 @@ init_per_group(ipv6, Config) -> init_per_group(ipv4_ipv6, Config) -> init_per_group_ipv6([inet, inet6], Config); %% -init_per_group(get, Config) -> +init_per_group(snmpget = Exec, Config) -> %% From Ubuntu package snmp - find_executable(snmpget, Config); -init_per_group(inform, Config) -> + init_per_group_agent(Exec, Config); +init_per_group(snmptrapd = Exec, Config) -> %% From Ubuntu package snmpd - find_executable(snmptrapd, Config); + init_per_group_agent(Exec, Config); +init_per_group(snmpd_mt, Config) -> + %% From Ubuntu package snmp + init_per_group_manager( + snmpd, + [{manager_net_if_module, snmpm_net_if_mt} | Config]); +init_per_group(snmpd = Exec, Config) -> + %% From Ubuntu package snmp + init_per_group_manager( + Exec, + [{manager_net_if_module, snmpm_net_if} | Config]); %% init_per_group(_, Config) -> Config. @@ -100,16 +122,20 @@ init_per_group(_, Config) -> init_per_group_ipv6(Families, Config) -> case ct:require(ipv6_hosts) of ok -> - init_per_group_ip(Families, Config); + case gen_udp:open(0, [inet6]) of + {ok, S} -> + ok = gen_udp:close(S), + init_per_group_ip(Families, Config); + {error, _} -> + {skip, "Host seems to not support IPv6"} + end; _ -> - {skip, "Host does not support IPV6"} + {skip, "Test config ipv6_hosts is missing"} end. init_per_group_ip(Families, Config) -> - Dir = ?config(priv_dir, Config), AgentPort = ?config(agent_port, Config), ManagerPort = ?config(manager_port, Config), - Versions = [v2], {ok, Host} = inet:gethostname(), Transports = [begin @@ -121,10 +147,22 @@ init_per_group_ip(Families, Config) -> {ok, Addr} = inet:getaddr(Host, Family), {domain(Family), {Addr, ManagerPort}} end || Family <- Families], + [{transports, Transports}, {targets, Targets} | Config]. + +init_per_group_agent(Exec, Config) -> + Versions = [v2], + Dir = ?config(priv_dir, Config), + Transports = ?config(transports, Config), + Targets = ?config(targets, Config), agent_config(Dir, Transports, Targets, Versions), - [{port, ?AGENT_PORT}, {snmp_versions, Versions}, - {transports, Transports}, {targets, Targets} - | Config]. + find_executable(Exec, [{snmp_versions, Versions} | Config]). + +init_per_group_manager(Exec, Config) -> + Versions = [v2], + Dir = ?config(priv_dir, Config), + Targets = ?config(targets, Config), + manager_config(Dir, Targets), + find_executable(Exec, [{snmp_versions, Versions} | Config]). @@ -132,7 +170,7 @@ end_per_group(_GroupName, Config) -> Config. init_per_testcase(_Case, Config) -> - Dog = ct:timetrap(10000), + Dog = ct:timetrap(20000), application:stop(snmp), application:unload(snmp), [{watchdog, Dog} | Config]. @@ -179,7 +217,12 @@ find_sys_executable(Exec, ExecStr, [Dir | Dirs], Config) -> start_agent(Config) -> ok = application:load(snmp), - ok = application:set_env(snmp, agent, app_env(Config)), + ok = application:set_env(snmp, agent, agent_app_env(Config)), + ok = application:start(snmp). + +start_manager(Config) -> + ok = application:load(snmp), + ok = application:set_env(snmp, manager, manager_app_env(Config)), ok = application:start(snmp). %%-------------------------------------------------------------------- @@ -199,6 +242,39 @@ erlang_agent_netsnmp_get(Config) when is_list(Config) -> ok. %%-------------------------------------------------------------------- +erlang_manager_netsnmp_get() -> + [{doc,"Test that the erlang snmp manager can access snmpnet agent"}]. + +erlang_manager_netsnmp_get(Config) when is_list(Config) -> + Community = "happy-testing", + SysDescr = "Net-SNMP agent", + TargetName = "Target Net-SNMP agent", + Transports = ?config(transports, Config), + ProgHandle = start_snmpd(Community, SysDescr, Config), + start_manager(Config), + snmp_manager_user:start_link(self(), test_user), + [snmp_manager_user:register_agent( + TargetName++domain_suffix(Domain), + [{reg_type, target_name}, + {tdomain, Domain}, {taddress, Addr}, + {community, Community}, {engine_id, "EngineId"}, + {version, v2}, {sec_model, v2c}, {sec_level, noAuthNoPriv}]) + || {Domain, Addr} <- Transports], + Results = + [snmp_manager_user:sync_get( + TargetName++domain_suffix(Domain), + [?sysDescr_instance]) + || {Domain, _} <- Transports], + ct:pal("sync_get -> ~p", [Results]), + snmp_manager_user:stop(), + stop_program(ProgHandle), + [{ok, + {noError, 0, + [{varbind, ?sysDescr_instance, 'OCTET STRING', SysDescr,1}] }, + _} = R || R <- Results], + ok. + +%%-------------------------------------------------------------------- erlang_agent_netsnmp_inform(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), Mib = "TestTrapv2", @@ -267,7 +343,29 @@ start_snmptrapd(Mibs, Config) -> {ok, StartCheckMP} = re:compile("NET-SNMP version ", [anchored]), start_program(snmptrapd, SnmptrapdArgs, StartCheckMP, Config). +start_snmpd(Community, SysDescr, Config) -> + DataDir = ?config(data_dir, Config), + Targets = ?config(targets, Config), + Transports = ?config(transports, Config), + Port = mk_port_number(), + CommunityArgs = + ["--rocommunity"++domain_suffix(Domain)++"=" + ++Community++" "++inet_parse:ntoa(Ip) + || {Domain, {Ip, _}} <- Targets], + SnmpdArgs = + ["-f", "-r", %"-Dverbose", + "-c", filename:join(DataDir, "snmpd.conf"), + "-C", "-Lo", + "-m", "", + "--sysDescr="++SysDescr, + "--agentXSocket=tcp:localhost:"++integer_to_list(Port)] + ++ CommunityArgs + ++ [net_snmp_addr_str(Transports)], + {ok, StartCheckMP} = re:compile("NET-SNMP version ", [anchored]), + start_program(snmpd, SnmpdArgs, StartCheckMP, Config). + start_program(Prog, Args, StartCheckMP, Config) -> + ct:pal("Starting program: ~w ~p", [Prog, Args]), Path = ?config(Prog, Config), DataDir = ?config(data_dir, Config), StartWrapper = filename:join(DataDir, "start_stop_wrapper"), @@ -318,12 +416,13 @@ wait_program_stop({Pid, Mon}) -> end. run_program(Parent, StartWrapper, ProgAndArgs) -> + [Prog | _] = ProgAndArgs, Port = open_port( {spawn_executable, StartWrapper}, [{args, ProgAndArgs}, binary, stderr_to_stdout, {line, 80}, exit_status]), - ct:pal("Prog ~p started: ~p", [Port, ProgAndArgs]), + ct:pal("Prog ~p started: ~p", [Port, Prog]), run_program_loop(Parent, Port, []). run_program_loop(Parent, Port, Buf) -> @@ -352,7 +451,7 @@ run_program_loop(Parent, Port, Buf) -> end. -app_env(Config) -> +agent_app_env(Config) -> Dir = ?config(priv_dir, Config), Vsns = ?config(snmp_versions, Config), [{versions, Vsns}, @@ -372,6 +471,20 @@ app_env(Config) -> {note_store, [{verbosity, silence}]}, {net_if, [{verbosity, trace}]}]. +manager_app_env(Config) -> + Dir = ?config(priv_dir, Config), + Vsns = ?config(snmp_versions, Config), + NetIfModule = ?config(manager_net_if_module, Config), + [{versions, Vsns}, + {audit_trail_log, [{type, read_write}, + {dir, Dir}, + {size, {10240, 10}}]}, + {net_if, [{module, NetIfModule}]}, + {config, [{dir, Dir}, + {db_dir, Dir}, + {verbosity, trace}]} + ]. + oid_str([1 | Ints]) -> "iso." ++ oid_str_tl(Ints); oid_str(Ints) -> @@ -384,9 +497,6 @@ oid_str_tl([Int]) -> oid_str_tl([Int | Ints]) -> integer_to_list(Int) ++ "." ++ oid_str_tl(Ints). -agent_config(Dir, Transports, TargetDomain, TargetAddr, Versions) -> - agent_config(Dir, Transports, [{TargetDomain, TargetAddr}], Versions). -%% agent_config(Dir, Transports, Targets, Versions) -> EngineID = ?AGENT_ENGINE_ID, MMS = ?DEFAULT_MAX_MESSAGE_SIZE, @@ -403,6 +513,11 @@ agent_config(Dir, Transports, Targets, Versions) -> ok = snmp_config:write_agent_snmp_notify_conf(Dir, inform), ok = snmp_config:write_agent_snmp_vacm_conf(Dir, Versions, none). +manager_config(Dir, Targets) -> + EngineID = ?MANAGER_ENGINE_ID, + MMS = ?DEFAULT_MAX_MESSAGE_SIZE, + ok = snmp_config:write_manager_snmp_conf(Dir, Targets, MMS, EngineID). + net_snmp_version([v3 | _]) -> "-v3"; net_snmp_version([v2 | _]) -> @@ -431,3 +546,14 @@ net_snmp_addr_str({transportDomainUdpIpv6, {Addr, Port}}) -> "udp6:[" ++ inet_parse:ntoa(Addr) ++ "]:" ++ integer_to_list(Port). + +domain_suffix(transportDomainUdpIpv4) -> + ""; +domain_suffix(transportDomainUdpIpv6) -> + "6". + +mk_port_number() -> + {ok, Socket} = gen_udp:open(0, [{reuseaddr, true}]), + {ok, PortNum} = inet:port(Socket), + ok = gen_udp:close(Socket), + PortNum. diff --git a/lib/snmp/test/snmp_to_snmpnet_SUITE_data/snmpd.conf b/lib/snmp/test/snmp_to_snmpnet_SUITE_data/snmpd.conf new file mode 100644 index 0000000000..2a5f31680f --- /dev/null +++ b/lib/snmp/test/snmp_to_snmpnet_SUITE_data/snmpd.conf @@ -0,0 +1,12 @@ +sysLocation On the lab network +sysContact otptest <[email protected]> + +createUser myinternaluser SHA dropdead + +agentSecName myinternaluser + +master agentx + +[snmp] +noPersistentLoad yes +noPersistentSave yes diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk index fd10244386..b436a79076 100644 --- a/lib/snmp/vsn.mk +++ b/lib/snmp/vsn.mk @@ -18,6 +18,6 @@ # %CopyrightEnd% APPLICATION = snmp -SNMP_VSN = 5.0 +SNMP_VSN = 5.1 PRE_VSN = APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)" diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 60440d3a80..f3db05192e 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -29,6 +29,153 @@ <file>notes.xml</file> </header> +<section><title>Ssh 3.0.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixes of login blocking after port scanning.</p> + <p> + Own Id: OTP-12247 Aux Id: seq12726 </p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 3.0.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Add option sftp_vsn to SFTP</p> + <p> + Own Id: OTP-12227</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Fix option user_interaction to work as expected. When + password authentication is implemented with ssh + keyboard-interactive method and the password is already + supplied, so that we do not need to query user, then + connections should succeed even though user_interaction + option is set to false.</p> + <p> + Own Id: OTP-11329 Aux Id: seq12420, seq12335 </p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 3.0.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Gracefully handle bad data from the client when expecting + ssh version exchange.</p> + <p> + Own Id: OTP-12157 Aux Id: seq12706 </p> + </item> + <item> + <p> + When restarting an ssh daemon, that was stopped with + ssh:stop_listner/ [1,2] new options given shall replace + old ones.</p> + <p> + Own Id: OTP-12168 Aux Id: seq12711 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + ssh now has a format_status function to avoid printing + sensitive information in error loggs.</p> + <p> + Own Id: OTP-12030</p> + </item> + </list> + </section> + + + <section><title>Known Bugs and Problems</title> + <list> + <item> + <p> + The option <c>parallel_login</c> didn't work with the + value <c>true</c>. All logins were serial.</p> + <p> + Own Id: OTP-12194</p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 3.0.5</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + When starting an ssh-daemon giving the option + {parallel_login, true}, the timeout for authentication + negotiation ({negotiation_timeout, integer()}) was never + removed.</p> + <p> + This caused the session to always be terminated after the + timeout if parallel_login was set.</p> + <p> + Own Id: OTP-12057 Aux Id: seq12663 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Warning: this is experimental and may disappear or change + without previous warning.</p> + <p> + Experimental support for running Quickcheck and PropEr + tests from common_test suites is added to common_test. + See the reference manual for the new module + <c>ct_property_testing</c>.</p> + <p> + Experimental property tests are added under + <c>lib/{inet,ssh}/test/property_test</c>. They can be run + directly or from the commont_test suites + <c>inet/ftp_property_test_SUITE.erl</c> and + <c>ssh/test/ssh_property_test_SUITE.erl</c>.</p> + <p> + See the code in the <c>test</c> directories and the man + page for details.</p> + <p> + (Thanks to Tuncer Ayaz for a patch adding Triq)</p> + <p> + Own Id: OTP-12119</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 3.0.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml index 72e7252536..ff72cf7ee0 100644 --- a/lib/ssh/doc/src/ssh_connection.xml +++ b/lib/ssh/doc/src/ssh_connection.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2013</year> + <year>2014</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -137,7 +137,7 @@ <tag><c><![CDATA[{pty, ssh_channel_id(), boolean() = WantReply, {string() = Terminal, integer() = CharWidth, - integer() = RowHeight, integer() = PixelWidth, integer() = PixelHight, + integer() = RowHeight, integer() = PixelWidth, integer() = PixelHeight, [{atom() | integer() = Opcode, integer() = Value}] = TerminalModes}}]]></c></tag> <item>A pseudo-terminal has been requested for the @@ -148,11 +148,11 @@ drawable area of the window. The <c>Opcode</c> in the <c>TerminalModes</c> list is the mnemonic name, represented as an lowercase erlang atom, defined in - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 8, - or the opcode if the mnemonic name is not listed in the + <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 8. + It may also be an opcode if the mnemonic name is not listed in the RFC. Example <c>OP code: 53, mnemonic name ECHO erlang atom: - echo</c>. There is currently no API function to generate this - event.</item> + echo</c>.This event is sent as result of calling <seealso + marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso></item> <tag><c><![CDATA[{shell, boolean() = WantReply}]]></c></tag> <item> This message will request that the user's default shell @@ -273,7 +273,52 @@ </desc> </func> - <func> + <func> + <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> success | failure</name> + <fsummary>Send status replies to requests that want such replies. </fsummary> + <type> + <v> ConnectionRef = ssh_connection_ref() </v> + <v> ChannelId = ssh_channel_id()</v> + <v> Options = proplists:proplist()</v> + </type> + <desc> + <p> Sends a SSH Connection Protocol pty_req, to allocate a pseudo tty. + Should be called by a SSH client process. + Options are: + </p> + + <taglist> + <tag>{term, string()}</tag> + <item> + Defaults to os:getenv("TERM") or "vt100" if it is undefined. + </item> + <tag>{width, integer()}</tag> + <item> + Defaults to 80 if pixel_width is not defined. + </item> + <tag>{height, integer()}</tag> + <item> + Defaults to 24 if pixel_height is not defined. + </item> + <tag>{pixel_width, integer()}</tag> + <item> + Is disregarded if width is defined. + </item> + <tag>{pixel_height, integer()}</tag> + <item> + Is disregarded if height is defined. + </item> + <tag>{pty_opts, [{posix_atom(), integer()}]}</tag> + <item> + Option may be an empty list, otherwise + see possible POSIX names in section 8 in <url href="http://www.ietf.org/rfc/rfc4254.txt"> RFC 4254</url>. + </item> + </taglist> + + </desc> + </func> + + <func> <name>reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name> <fsummary>Send status replies to requests that want such replies. </fsummary> <type> diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index e55d092fe2..f1091e9eca 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2005</year><year>2013</year> + <year>2005</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -81,6 +81,17 @@ <p>The timeout is passed to the ssh_channel start function, and defaults to infinity.</p> </item> + <tag> + <p><c><![CDATA[{sftp_vsn, integer()}]]></c></p> + </tag> + <item> + <p> + Desired SFTP protocol version. + The actual version will be the minimum of + the desired version and the maximum supported + versions by the SFTP server. + </p> + </item> </taglist> <p>All other options are directly passed to <seealso marker="ssh">ssh:connect/3</seealso> or ignored if a diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 2ef2859fd7..90d71107ad 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -65,6 +65,7 @@ MODULES= \ ssh_cli \ ssh_file \ ssh_io \ + ssh_info \ ssh_math \ ssh_message \ ssh_no_io \ @@ -115,7 +116,7 @@ $(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) clean: - rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES) rm -f errs core *~ $(APP_TARGET): $(APP_SRC) ../vsn.mk diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index e0a51b3574..4ad55b34ca 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -23,6 +23,7 @@ sshd_sup, ssh_file, ssh_io, + ssh_info, ssh_math, ssh_no_io, ssh_server_key_api, diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 8269f89e40..600c01454c 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,25 +19,49 @@ {"%VSN%", [ - {"3.0.2", [{load_module, ssh_message, soft_purge, soft_purge, []}, + {"3.0.7", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_io, soft_purge, soft_purge, []}]}, - {"3.0.1", [{load_module, ssh, soft_purge, soft_purge, []}, - {load_module, ssh_acceptor, soft_purge, soft_purge, []}, - {load_module, ssh_message, soft_purge, soft_purge, []}, + {load_module, ssh_info, soft_purge, soft_purge, []}, + {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]}, + {"3.0.6", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_io, soft_purge, soft_purge, []}]}, + {load_module, ssh_info, soft_purge, soft_purge, []}, + {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]}, {<<".*">>, [{restart_application, ssh}]} ], [ - {"3.0.2", [{load_module, ssh_message, soft_purge, soft_purge, []}, + {"3.0.7", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_io, soft_purge, soft_purge, []}]}, - {"3.0.1", [{load_module, ssh, soft_purge, soft_purge, []}, - {load_module, ssh_acceptor, soft_purge, soft_purge, []}, - {load_module, ssh_message, soft_purge, soft_purge, []}, + {load_module, ssh_info, soft_purge, soft_purge, []}, + {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]}, + {"3.0.6", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_io, soft_purge, soft_purge, []}]}, + {load_module, ssh_info, soft_purge, soft_purge, []}, + {load_module, ssh_message, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_io, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_sftp, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_xfer, soft_purge, soft_purge, [ssh_connection_handler]}]}, {<<".*">>, [{restart_application, ssh}]} ] }. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 8a8d4bb89e..eae33e3683 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -194,6 +194,7 @@ shell(Host, Port, Options) -> {ok, ConnectionRef} -> case ssh_connection:session_channel(ConnectionRef, infinity) of {ok,ChannelId} -> + success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []), Args = [{channel_cb, ssh_shell}, {init_args,[ConnectionRef, ChannelId]}, {cm, ConnectionRef}, {channel_id, ChannelId}], @@ -234,22 +235,26 @@ do_start_daemon(Host, Port, Options, SocketOptions) -> {port, Port}, {role, server}, {socket_opts, SocketOptions}, {ssh_opts, Options}]) of - {ok, SysSup} -> - {ok, SysSup}; {error, {already_started, _}} -> {error, eaddrinuse}; - {error, R} -> - {error, R} + Result = {Code, _} when (Code == ok) or (Code == error) -> + Result catch exit:{noproc, _} -> {error, ssh_not_started} end; Sup -> - case ssh_system_sup:restart_acceptor(Host, Port) of + AccPid = ssh_system_sup:acceptor_supervisor(Sup), + case ssh_acceptor_sup:start_child(AccPid, [{address, Host}, + {port, Port}, {role, server}, + {socket_opts, SocketOptions}, + {ssh_opts, Options}]) of + {error, {already_started, _}} -> + {error, eaddrinuse}; {ok, _} -> {ok, Sup}; - _ -> - {error, eaddrinuse} + Other -> + Other end end. diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 7302196674..6c443eeb9c 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -22,7 +22,8 @@ -module(ssh_acceptor). %% Internal application API --export([start_link/5]). +-export([start_link/5, + number_of_connections/1]). %% spawn export -export([acceptor_init/6, acceptor_loop/6]). @@ -140,5 +141,6 @@ handle_error(Reason) -> number_of_connections(SystemSup) -> length([X || {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup), + is_pid(X), is_reference(R) ]). diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl index 2be729d305..46fdef07d0 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -26,7 +26,7 @@ -module(ssh_acceptor_sup). -behaviour(supervisor). --export([start_link/1, start_child/2, stop_child/2]). +-export([start_link/1, start_child/2, stop_child/3]). %% Supervisor callback -export([init/1]). @@ -45,18 +45,17 @@ start_child(AccSup, ServerOpts) -> {error, already_present} -> Address = proplists:get_value(address, ServerOpts), Port = proplists:get_value(port, ServerOpts), - Name = id(Address, Port), - supervisor:delete_child(?MODULE, Name), + stop_child(AccSup, Address, Port), supervisor:start_child(AccSup, Spec); Reply -> Reply end. -stop_child(Address, Port) -> +stop_child(AccSup, Address, Port) -> Name = id(Address, Port), - case supervisor:terminate_child(?MODULE, Name) of + case supervisor:terminate_child(AccSup, Name) of ok -> - supervisor:delete_child(?MODULE, Name); + supervisor:delete_child(AccSup, Name); Error -> Error end. diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 45fd907383..45c4d52d7e 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -119,8 +119,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> false -> FirstAlg = proplists:get_value(public_key_alg, Opts, ?PREFERRED_PK_ALG), SecondAlg = other_alg(FirstAlg), - AllowUserInt = proplists:get_value(user_interaction, Opts, true), - Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt), + Prefs = method_preference(FirstAlg, SecondAlg), ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, userauth_preference = Prefs, userauth_methods = none, @@ -130,15 +129,13 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> case length(Algs) =:= 2 of true -> SecondAlg = other_alg(FirstAlg), - AllowUserInt = proplists:get_value(user_interaction, Opts, true), - Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt), + Prefs = method_preference(FirstAlg, SecondAlg), ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, userauth_preference = Prefs, userauth_methods = none, service = "ssh-connection"}); _ -> - AllowUserInt = proplists:get_value(user_interaction, Opts, true), - Prefs = method_preference(FirstAlg, AllowUserInt), + Prefs = method_preference(FirstAlg), ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, userauth_preference = Prefs, userauth_methods = none, @@ -187,9 +184,8 @@ handle_userauth_request(#ssh_msg_service_request{name = handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "password", - data = Data}, _, + data = <<?FALSE, ?UINT32(Sz), BinPwd:Sz/binary>>}, _, #ssh{opts = Opts} = Ssh) -> - <<_:8, ?UINT32(Sz), BinPwd:Sz/binary>> = Data, Password = unicode:characters_to_list(BinPwd), case check_password(User, Password, Opts) of true -> @@ -204,6 +200,27 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", + method = "password", + data = <<?TRUE, + _/binary + %% ?UINT32(Sz1), OldBinPwd:Sz1/binary, + %% ?UINT32(Sz2), NewBinPwd:Sz2/binary + >> + }, _, + Ssh) -> + %% Password change without us having sent SSH_MSG_USERAUTH_PASSWD_CHANGEREQ (because we never do) + %% RFC 4252 says: + %% SSH_MSG_USERAUTH_FAILURE without partial success - The password + %% has not been changed. Either password changing was not supported, + %% or the old password was bad. + + {not_authorized, {User, {error,"Password change not supported"}}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = "", + partial_success = false}, Ssh)}; + +handle_userauth_request(#ssh_msg_userauth_request{user = User, + service = "ssh-connection", method = "none"}, _, #ssh{userauth_supported_methods = Methods} = Ssh) -> {not_authorized, {User, undefined}, @@ -256,15 +273,12 @@ handle_userauth_info_request( data = Data}, IoCb, #ssh{opts = Opts} = Ssh) -> PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data), - Resps = keyboard_interact_get_responses(IoCb, Opts, + Responses = keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos), - RespBin = list_to_binary( - lists:map(fun(S) -> <<?STRING(list_to_binary(S))>> end, - Resps)), {ok, ssh_transport:ssh_packet( #ssh_msg_userauth_info_response{num_responses = NumPrompts, - data = RespBin}, Ssh)}. + data = Responses}, Ssh)}. handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> @@ -276,25 +290,16 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -method_preference(Alg1, Alg2, true) -> +method_preference(Alg1, Alg2) -> [{"publickey", ?MODULE, publickey_msg, [Alg1]}, {"publickey", ?MODULE, publickey_msg,[Alg2]}, {"password", ?MODULE, password_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} - ]; -method_preference(Alg1, Alg2, false) -> - [{"publickey", ?MODULE, publickey_msg, [Alg1]}, - {"publickey", ?MODULE, publickey_msg,[Alg2]}, - {"password", ?MODULE, password_msg, []} ]. -method_preference(Alg1, true) -> +method_preference(Alg1) -> [{"publickey", ?MODULE, publickey_msg, [Alg1]}, {"password", ?MODULE, password_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} - ]; -method_preference(Alg1, false) -> - [{"publickey", ?MODULE, publickey_msg, [Alg1]}, - {"password", ?MODULE, password_msg, []} ]. user_name(Opts) -> @@ -362,35 +367,29 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) -> algorithm_string('ssh-rsa') -> "ssh-rsa"; algorithm_string('ssh-dss') -> - "ssh-dss". + "ssh-dss". decode_keyboard_interactive_prompts(_NumPrompts, Data) -> ssh_message:decode_keyboard_interactive_prompts(Data, []). keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> NumPrompts = length(PromptInfos), - case proplists:get_value(keyboard_interact_fun, Opts) of - undefined when NumPrompts == 1 -> - %% Special case/fallback for just one prompt - %% (assumed to be the password prompt) - case proplists:get_value(password, Opts) of - undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); - PW -> [PW] - end; - undefined -> - keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); - KbdInteractFun -> - Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, - PromptInfos), - case KbdInteractFun(Name, Instr, Prompts) of - Rs when length(Rs) == NumPrompts -> - Rs; - Rs -> - erlang:error({mismatching_number_of_responses, - {got,Rs}, - {expected,NumPrompts}}) - end - end. + keyboard_interact_get_responses(proplists:get_value(user_interaction, Opts, true), + proplists:get_value(keyboard_interact_fun, Opts), + proplists:get_value(password, Opts, undefined), IoCb, Name, + Instr, PromptInfos, Opts, NumPrompts). + +keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _, + 1) when Password =/= undefined -> + [Password]; %% Password auth implemented with keyboard-interaction and passwd is known +keyboard_interact_get_responses(_, _, _, _, _, _, _, _, 0) -> + [""]; +keyboard_interact_get_responses(false, undefined, undefined, _, _, _, [Prompt|_], Opts, _) -> + ssh_no_io:read_line(Prompt, Opts); %% Throws error as keyboard interaction is not allowed +keyboard_interact_get_responses(true, undefined, _,IoCb, Name, Instr, PromptInfos, Opts, _) -> + keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); +keyboard_interact_get_responses(true, Fun, _, Name, Instr, PromptInfos, _, _, NumPrompts) -> + keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts). keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> if Name /= "" -> IoCb:format("~s", [Name]); @@ -404,6 +403,21 @@ keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> end, Prompts). +keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> + Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, + PromptInfos), + case KbdInteractFun(Name, Instr, Prompts) of + Rs when length(Rs) == NumPrompts -> + Rs; + Rs -> + throw({mismatching_number_of_responses, + {got,Rs}, + {expected, NumPrompts}, + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "User interaction failed", + language = "en"}}) + end. + other_alg('ssh-rsa') -> 'ssh-dss'; other_alg('ssh-dss') -> diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 508ae637cf..5c24f362b1 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -67,7 +67,8 @@ %% Internal application API -export([cache_create/0, cache_lookup/2, cache_update/2, cache_delete/1, cache_delete/2, cache_foldl/3, - cache_find/2]). + cache_find/2, + get_print_info/1]). -record(state, { cm, @@ -190,6 +191,14 @@ init([Options]) -> %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- +handle_call(get_print_info, _From, State) -> + Reply = + {{State#state.cm, + State#state.channel_id}, + io_lib:format('CB=~p',[State#state.channel_cb]) + }, + {reply, Reply, State}; + handle_call(Request, From, #state{channel_cb = Module, channel_state = ChannelState} = State) -> try Module:handle_call(Request, From, ChannelState) of @@ -333,6 +342,9 @@ cache_find(ChannelPid, Cache) -> Channel end. +get_print_info(Pid) -> + call(Pid, get_print_info, 1000). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 8421b07167..d14f7ce27d 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2013. All Rights Reserved. +%% Copyright Ericsson AB 2005-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -26,6 +26,7 @@ -define(DEFAULT_PACKET_SIZE, 32768). -define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE). -define(DEFAULT_TIMEOUT, 5000). +-define(MAX_PROTO_VERSION, 255). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @@ -164,6 +165,10 @@ recipient_channel }). +-define(TERMINAL_WIDTH, 80). +-define(TERMINAL_HEIGHT, 24). +-define(DEFAULT_TERMINAL, "vt100"). + -define(TTY_OP_END,0). %% Indicates end of options. -define(VINTR,1). %% Interrupt character; 255 if none. Similarly for the %% other characters. Not all of these characters are diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 33849f4527..593443e11c 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -32,11 +32,11 @@ %% API -export([session_channel/2, session_channel/4, exec/4, shell/2, subsystem/4, send/3, send/4, send/5, - send_eof/2, adjust_window/3, setenv/5, close/2, reply_request/4]). + send_eof/2, adjust_window/3, setenv/5, close/2, reply_request/4, + ptty_alloc/3, ptty_alloc/4]). %% Potential API currently unsupported and not tested --export([open_pty/3, open_pty/7, - open_pty/9, window_change/4, window_change/6, +-export([window_change/4, window_change/6, direct_tcpip/6, direct_tcpip/8, tcpip_forward/3, cancel_tcpip_forward/3, signal/3, exit_status/3]). @@ -107,9 +107,15 @@ shell(ConnectionHandler, ChannelId) -> %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> - ssh_connection_handler:request(ConnectionHandler, self(), - ChannelId, "subsystem", - true, [?string(SubSystem)], TimeOut). + case ssh_connection_handler:request(ConnectionHandler, self(), + ChannelId, "subsystem", + true, [?string(SubSystem)], TimeOut) of + success -> success; + failure -> failure; + {error,timeout} -> {error,timeout}; + _ -> failure + end. + %%-------------------------------------------------------------------- -spec send(pid(), channel_id(), iodata()) -> ok | {error, closed}. @@ -183,6 +189,25 @@ reply_request(_,false, _, _) -> ok. %%-------------------------------------------------------------------- +-spec ptty_alloc(pid(), channel_id(), proplists:proplist()) -> success | failiure. +%% +%% +%% Description: Sends a ssh connection protocol pty_req. +%%-------------------------------------------------------------------- +ptty_alloc(ConnectionHandler, Channel, Options) -> + ptty_alloc(ConnectionHandler, Channel, Options, infinity). +ptty_alloc(ConnectionHandler, Channel, Options, TimeOut) -> + {Width, PixWidth} = pty_default_dimensions(width, Options), + {Hight, PixHight} = pty_default_dimensions(hight, Options), + pty_req(ConnectionHandler, Channel, + proplists:get_value(term, Options, default_term()), + proplists:get_value(width, Options, Width), + proplists:get_value(hight, Options, Hight), + proplists:get_value(pixel_widh, Options, PixWidth), + proplists:get_value(pixel_hight, Options, PixHight), + proplists:get_value(pty_opts, Options, []), TimeOut + ). +%%-------------------------------------------------------------------- %% Not yet officialy supported! The following functions are part of the %% initial contributed ssh application. They are untested. Do we want them? %% Should they be documented and tested? @@ -205,23 +230,6 @@ exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). -open_pty(ConnectionHandler, Channel, TimeOut) -> - open_pty(ConnectionHandler, Channel, - os:getenv("TERM"), 80, 24, [], TimeOut). - -open_pty(ConnectionHandler, Channel, Term, Width, Height, PtyOpts, TimeOut) -> - open_pty(ConnectionHandler, Channel, Term, Width, - Height, 0, 0, PtyOpts, TimeOut). - -open_pty(ConnectionHandler, Channel, Term, Width, Height, - PixWidth, PixHeight, PtyOpts, TimeOut) -> - ssh_connection_handler:request(ConnectionHandler, - Channel, "pty-req", true, - [?string(Term), - ?uint32(Width), ?uint32(Height), - ?uint32(PixWidth),?uint32(PixHeight), - encode_pty_opts(PtyOpts)], TimeOut). - direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, Timeout) -> direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, @@ -1074,6 +1082,27 @@ flow_control([_|_], #channel{flow_control = From, flow_control(_,_,_) -> []. +pty_req(ConnectionHandler, Channel, Term, Width, Height, + PixWidth, PixHeight, PtyOpts, TimeOut) -> + ssh_connection_handler:request(ConnectionHandler, + Channel, "pty-req", true, + [?string(Term), + ?uint32(Width), ?uint32(Height), + ?uint32(PixWidth),?uint32(PixHeight), + encode_pty_opts(PtyOpts)], TimeOut). + +pty_default_dimensions(Dimension, Options) -> + case proplists:get_value(Dimension, Options, 0) of + N when is_integer(N), N > 0 -> + {N, 0}; + _ -> + case proplists:get_value(list_to_atom("pixel_" ++ atom_to_list(Dimension)), Options, 0) of + N when is_integer(N), N > 0 -> + {0, N}; + _ -> + {?TERMINAL_WIDTH, 0} + end + end. encode_pty_opts(Opts) -> Bin = list_to_binary(encode_pty_opts2(Opts)), @@ -1271,3 +1300,10 @@ decode_ip(Addr) when is_binary(Addr) -> {ok,A} -> A end. +default_term() -> + case os:getenv("TERM") of + false -> + ?DEFAULT_TERMINAL; + Str when is_list(Str)-> + Str + end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 86804c4436..8b7c4a5f80 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -41,14 +41,16 @@ global_request/4, send/5, send_eof/2, info/1, info/2, connection_info/2, channel_info/3, adjust_window/3, close/2, stop/1, renegotiate/1, renegotiate_data/1, - start_connection/4]). + start_connection/4, + get_print_info/1]). %% gen_fsm callbacks -export([hello/2, kexinit/2, key_exchange/2, new_keys/2, - userauth/2, connected/2]). + userauth/2, connected/2, + error/2]). -export([init/1, handle_event/3, - handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). + handle_sync_event/4, handle_info/3, terminate/3, format_status/2, code_change/4]). -record(state, { role, @@ -71,7 +73,8 @@ connection_queue, address, port, - opts + opts, + recbuf }). -type state_name() :: hello | kexinit | key_exchange | new_keys | userauth | connection. @@ -103,12 +106,22 @@ start_connection(client = Role, Socket, Options, Timeout) -> end; start_connection(server = Role, Socket, Options, Timeout) -> + SSH_Opts = proplists:get_value(ssh_opts, Options, []), try - case proplists:get_value(parallel_login, Options, false) of + case proplists:get_value(parallel_login, SSH_Opts, false) of true -> - spawn(fun() -> start_server_connection(Role, Socket, Options, Timeout) end); + HandshakerPid = + spawn_link(fun() -> + receive + {do_handshake, Pid} -> + handshake(Pid, erlang:monitor(process,Pid), Timeout) + end + end), + ChildPid = start_the_connection_child(HandshakerPid, Role, Socket, Options), + HandshakerPid ! {do_handshake, ChildPid}; false -> - start_server_connection(Role, Socket, Options, Timeout) + ChildPid = start_the_connection_child(self(), Role, Socket, Options), + handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout) end catch exit:{noproc, _} -> @@ -117,16 +130,14 @@ start_connection(server = Role, Socket, Options, Timeout) -> {error, Error} end. - -start_server_connection(server = Role, Socket, Options, Timeout) -> +start_the_connection_child(UserPid, Role, Socket, Options) -> Sups = proplists:get_value(supervisors, Options), ConnectionSup = proplists:get_value(connection_sup, Sups), - Opts = [{supervisors, Sups}, {user_pid, self()} | proplists:get_value(ssh_opts, Options, [])], + Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])], {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]), {_, Callback, _} = proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), socket_control(Socket, Pid, Callback), - Ref = erlang:monitor(process, Pid), - handshake(Pid, Ref, Timeout). + Pid. start_link(Role, Socket, Options) -> @@ -162,9 +173,23 @@ init([Role, Socket, SshOpts]) -> State#state{ssh_params = Ssh}) catch _:Error -> - gen_fsm:enter_loop(?MODULE, [], error, {Error, State0}) + gen_fsm:enter_loop(?MODULE, [], error, {Error, State}) end. +%% Temporary fix for the Nessus error. SYN-> <-SYNACK ACK-> RST-> ? +error(_Event, {Error,State=#state{}}) -> + case Error of + {badmatch,{error,enotconn}} -> + %% {error,enotconn} probably from inet:peername in + %% init_ssh(server,..)/5 called from init/1 + {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}, State}; + _ -> + {stop, {shutdown,{init,Error}}, State} + end; +error(Event, State) -> + %% State deliberately not checked beeing #state. This is a panic-clause... + {stop, {shutdown,{init,{spurious_error,Event}}}, State}. + %%-------------------------------------------------------------------- -spec open_channel(pid(), string(), iodata(), integer(), integer(), timeout()) -> {open, channel_id()} | {error, term()}. @@ -231,6 +256,9 @@ send_eof(ConnectionHandler, ChannelId) -> %%-------------------------------------------------------------------- -spec connection_info(pid(), [atom()]) -> proplists:proplist(). %%-------------------------------------------------------------------- +get_print_info(ConnectionHandler) -> + sync_send_all_state_event(ConnectionHandler, get_print_info, 1000). + connection_info(ConnectionHandler, Options) -> sync_send_all_state_event(ConnectionHandler, {connection_info, Options}). @@ -293,28 +321,39 @@ info(ConnectionHandler, ChannelProcess) -> hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) -> VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)), send_msg(VsnMsg, State), - inet:setopts(Socket, [{packet, line}, {active, once}]), - {next_state, hello, State}; + {ok, [{recbuf, Size}]} = inet:getopts(Socket, [recbuf]), + inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]), + {next_state, hello, State#state{recbuf = Size}}; -hello({info_line, _Line},#state{socket = Socket} = State) -> +hello({info_line, _Line},#state{role = client, socket = Socket} = State) -> + %% The server may send info lines before the version_exchange inet:setopts(Socket, [{active, once}]), {next_state, hello, State}; +hello({info_line, _Line},#state{role = server} = State) -> + DisconnectMsg = + #ssh_msg_disconnect{code = + ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "Did not receive expected protocol version exchange", + language = "en"}, + handle_disconnect(DisconnectMsg, State); + hello({version_exchange, Version}, #state{ssh_params = Ssh0, - socket = Socket} = State) -> + socket = Socket, + recbuf = Size} = State) -> {NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version), case handle_version(NumVsn, StrVsn, Ssh0) of {ok, Ssh1} -> - inet:setopts(Socket, [{packet,0}, {mode,binary}, {active, once}]), + inet:setopts(Socket, [{packet,0}, {mode,binary}, {active, once}, {recbuf, Size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), send_msg(SshPacket, State), {next_state, kexinit, next_packet(State#state{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg})}; not_supported -> - DisconnectMsg = + DisconnectMsg = #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, + ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, description = "Protocol version " ++ StrVsn ++ " not supported", language = "en"}, @@ -530,7 +569,7 @@ connected({#ssh_msg_kexinit{}, _Payload} = Event, State) -> %%-------------------------------------------------------------------- handle_event(#ssh_msg_disconnect{description = Desc} = DisconnectMsg, _StateName, #state{} = State) -> - handle_disconnect(DisconnectMsg, State), + handle_disconnect(peer, DisconnectMsg, State), {stop, {shutdown, Desc}, State}; handle_event(#ssh_msg_ignore{}, StateName, State) -> @@ -585,7 +624,7 @@ handle_event(renegotiate, connected, #state{ssh_params = Ssh0} renegotiate = true})}; handle_event(renegotiate, StateName, State) -> - timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiatie]), + timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]), %% Allready in keyexcahange so ignore {next_state, StateName, State}; @@ -738,6 +777,20 @@ handle_sync_event({recv_window, ChannelId}, _From, StateName, end, {reply, Reply, StateName, next_packet(State)}; +handle_sync_event(get_print_info, _From, StateName, State) -> + Reply = + try + {inet:sockname(State#state.socket), + inet:peername(State#state.socket) + } + of + {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])}; + _ -> {{"-",0},"-"} + catch + _:_ -> {{"?",0},"?"} + end, + {reply, Reply, StateName, State}; + handle_sync_event({connection_info, Options}, _From, StateName, State) -> Info = ssh_info(Options, State, []), {reply, Info, StateName, State}; @@ -916,6 +969,10 @@ terminate(normal, _, #state{transport_cb = Transport, (catch Transport:close(Socket)), ok; +terminate({shutdown,{init,Reason}}, StateName, State) -> + error_logger:info_report(io_lib:format("Erlang ssh in connection handler init: ~p~n",[Reason])), + terminate(normal, StateName, State); + %% Terminated by supervisor terminate(shutdown, StateName, #state{ssh_params = Ssh0} = State) -> DisconnectMsg = @@ -931,8 +988,10 @@ terminate({shutdown, #ssh_msg_disconnect{} = Msg}, StateName, {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), send_msg(SshPacket, State), terminate(normal, StateName, State#state{ssh_params = Ssh}); + terminate({shutdown, _}, StateName, State) -> terminate(normal, StateName, State); + terminate(Reason, StateName, #state{ssh_params = Ssh0, starter = _Pid, connection_state = Connection} = State) -> terminate_subsytem(Connection), @@ -945,12 +1004,43 @@ terminate(Reason, StateName, #state{ssh_params = Ssh0, starter = _Pid, send_msg(SshPacket, State), terminate(normal, StateName, State#state{ssh_params = Ssh}). + terminate_subsytem(#connection{system_supervisor = SysSup, sub_system_supervisor = SubSysSup}) when is_pid(SubSysSup) -> ssh_system_sup:stop_subsystem(SysSup, SubSysSup); terminate_subsytem(_) -> ok. +format_status(normal, [_, State]) -> + [{data, [{"StateData", State}]}]; +format_status(terminate, [_, State]) -> + SshParams0 = (State#state.ssh_params), + SshParams = SshParams0#ssh{c_keyinit = "***", + s_keyinit = "***", + send_mac_key = "***", + send_mac_size = "***", + recv_mac_key = "***", + recv_mac_size = "***", + encrypt_keys = "***", + encrypt_ctx = "***", + decrypt_keys = "***", + decrypt_ctx = "***", + compress_ctx = "***", + decompress_ctx = "***", + shared_secret = "***", + exchanged_hash = "***", + session_id = "***", + keyex_key = "***", + keyex_info = "***", + available_host_keys = "***"}, + [{data, [{"StateData", State#state{decoded_data_buffer = "***", + encoded_data_buffer = "***", + key_exchange_init_msg = "***", + opts = "***", + recbuf = "***", + ssh_params = SshParams + }}]}]. + %%-------------------------------------------------------------------- -spec code_change(OldVsn::term(), state_name(), Oldstate::term(), Extra::term()) -> {ok, state_name(), #state{}}. @@ -1111,7 +1201,10 @@ send_all_state_event(FsmPid, Event) -> gen_fsm:send_all_state_event(FsmPid, Event). sync_send_all_state_event(FsmPid, Event) -> - try gen_fsm:sync_send_all_state_event(FsmPid, Event, infinity) + sync_send_all_state_event(FsmPid, Event, infinity). + +sync_send_all_state_event(FsmPid, Event, Timeout) -> + try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout) catch exit:{noproc, _} -> {error, closed}; @@ -1208,13 +1301,23 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName, generate_event(Msg, StateName, State0, EncData) -> Event = ssh_message:decode(Msg), State = generate_event_new_state(State0, EncData), - case Event of - #ssh_msg_kexinit{} -> - %% We need payload for verification later. - event({Event, Msg}, StateName, State); - _ -> - event(Event, StateName, State) - end. + try + case Event of + #ssh_msg_kexinit{} -> + %% We need payload for verification later. + event({Event, Msg}, StateName, State); + _ -> + event(Event, StateName, State) + end + catch + _:_ -> + DisconnectMsg = + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "Encountered unexpected input", + language = "en"}, + handle_disconnect(DisconnectMsg, State) + end. + handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, @@ -1392,17 +1495,27 @@ handle_ssh_packet(Length, StateName, #state{decoded_data_buffer = DecData0, handle_disconnect(DisconnectMsg, State0) end. -handle_disconnect(#ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, - role = Role} = State0) -> +handle_disconnect(DisconnectMsg, State) -> + handle_disconnect(own, DisconnectMsg, State). + +handle_disconnect(#ssh_msg_disconnect{} = DisconnectMsg, State, Error) -> + handle_disconnect(own, DisconnectMsg, State, Error); +handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, role = Role} = State0) -> {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role), - State = send_replies(Replies, State0), + State = send_replies(disconnect_replies(Type, Msg, Replies), State0), {stop, {shutdown, Desc}, State#state{connection_state = Connection}}. -handle_disconnect(#ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, - role = Role} = State0, ErrorMsg) -> + +handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, + role = Role} = State0, ErrorMsg) -> {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role), - State = send_replies(Replies, State0), + State = send_replies(disconnect_replies(Type, Msg, Replies), State0), {stop, {shutdown, {Desc, ErrorMsg}}, State#state{connection_state = Connection}}. +disconnect_replies(own, Msg, Replies) -> + [{connection_reply, Msg} | Replies]; +disconnect_replies(peer, _, Replies) -> + Replies. + counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> Ssh#ssh{c_vsn = NumVsn , c_version = StrVsn}; counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) -> diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl new file mode 100644 index 0000000000..9ed598b3ab --- /dev/null +++ b/lib/ssh/src/ssh_info.erl @@ -0,0 +1,193 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Print some info of a running ssh aplication. +%%---------------------------------------------------------------------- + +-module(ssh_info). + +-compile(export_all). + +print() -> + try supervisor:which_children(ssh_sup) + of + _ -> + io:nl(), + print_general(), + io:nl(), + underline("Client part", $=), + print_clients(), + io:nl(), + underline("Server part", $=), + print_servers(), + io:nl(), + %% case os:type() of + %% {unix,_} -> + %% io:nl(), + %% underline("Linux part", $=), + %% underline("Listening"), + %% catch io:format(os:cmd("netstat -tpln")), + %% io:nl(), + %% underline("Other"), + %% catch io:format(os:cmd("netstat -tpn")); + %% _ -> ok + %% end, + underline("Supervisors", $=), + walk_sups(ssh_sup), + io:nl() + catch + _:_ -> + io:format("Ssh not found~n",[]) + end. + +%%%================================================================ +print_general() -> + {_Name, Slogan, Ver} = lists:keyfind(ssh,1,application:which_applications()), + underline(io_lib:format("~s ~s", [Slogan, Ver]), $=), + io:format('This printout is generated ~s. ~n',[datetime()]). + +%%%================================================================ +print_clients() -> + try + lists:foreach(fun print_client/1, supervisor:which_children(sshc_sup)) + catch + C:E -> + io:format('***FAILED: ~p:~p~n',[C,E]) + end. + +print_client({undefined,Pid,supervisor,[ssh_connection_handler]}) -> + {{Local,Remote},_Str} = ssh_connection_handler:get_print_info(Pid), + io:format(" Local=~s Remote=~s~n",[fmt_host_port(Local),fmt_host_port(Remote)]); +print_client(Other) -> + io:format(" [[Other 1: ~p]]~n",[Other]). + + +%%%================================================================ +print_servers() -> + try + lists:foreach(fun print_server/1, supervisor:which_children(sshd_sup)) + catch + C:E -> + io:format('***FAILED: ~p:~p~n',[C,E]) + end. + +print_server({{server,ssh_system_sup,LocalHost,LocalPort},Pid,supervisor,[ssh_system_sup]}) when is_pid(Pid) -> + io:format('Local=~s (~p children)~n',[fmt_host_port({LocalHost,LocalPort}), + ssh_acceptor:number_of_connections(Pid)]), + lists:foreach(fun print_system_sup/1, supervisor:which_children(Pid)); +print_server(Other) -> + io:format(" [[Other 2: ~p]]~n",[Other]). + +print_system_sup({Ref,Pid,supervisor,[ssh_subsystem_sup]}) when is_reference(Ref), + is_pid(Pid) -> + lists:foreach(fun print_channels/1, supervisor:which_children(Pid)); +print_system_sup({{ssh_acceptor_sup,LocalHost,LocalPort}, Pid,supervisor, [ssh_acceptor_sup]}) when is_pid(Pid) -> + io:format(" [Acceptor for ~s]~n",[fmt_host_port({LocalHost,LocalPort})]); +print_system_sup(Other) -> + io:format(" [[Other 3: ~p]]~n",[Other]). + +print_channels({{server,ssh_channel_sup,_,_},Pid,supervisor,[ssh_channel_sup]}) when is_pid(Pid) -> + lists:foreach(fun print_channel/1, supervisor:which_children(Pid)); +print_channels(Other) -> + io:format(" [[Other 4: ~p]]~n",[Other]). + + +print_channel({Ref,Pid,worker,[ssh_channel]}) when is_reference(Ref), + is_pid(Pid) -> + {{ConnManager,ChannelID}, Str} = ssh_channel:get_print_info(Pid), + {{Local,Remote},StrM} = ssh_connection_handler:get_print_info(ConnManager), + io:format(' ch ~p: ~s ~s',[ChannelID, StrM, Str]), + io:format(" Local=~s Remote=~s~n",[fmt_host_port(Local),fmt_host_port(Remote)]); +print_channel(Other) -> + io:format(" [[Other 5: ~p]]~n",[Other]). + +%%%================================================================ +-define(inc(N), (N+4)). + +walk_sups(StartPid) -> + io:format("Start at ~p, ~s.~n",[StartPid,dead_or_alive(StartPid)]), + walk_sups(children(StartPid), _Indent=?inc(0)). + +walk_sups([H={_,Pid,SupOrWorker,_}|T], Indent) -> + indent(Indent), io:format('~200p ~p is ~s~n',[H,Pid,dead_or_alive(Pid)]), + case SupOrWorker of + supervisor -> walk_sups(children(Pid), ?inc(Indent)); + _ -> ok + end, + walk_sups(T, Indent); +walk_sups([], _) -> + ok. + +dead_or_alive(Name) when is_atom(Name) -> + case whereis(Name) of + undefined -> + "**UNDEFINED**"; + Pid -> + dead_or_alive(Pid) + end; +dead_or_alive(Pid) when is_pid(Pid) -> + case process_info(Pid) of + undefined -> "**DEAD**"; + _ -> "alive" + end. + +indent(I) -> io:format('~*c',[I,$ ]). + +children(Pid) -> + Parent = self(), + Helper = spawn(fun() -> + Parent ! {self(),supervisor:which_children(Pid)} + end), + receive + {Helper,L} when is_list(L) -> + L + after + 2000 -> + catch exit(Helper, kill), + [] + end. + +%%%================================================================ +underline(Str) -> + underline(Str, $-). + +underline(Str, LineChar) -> + Len = lists:flatlength(Str), + io:format('~s~n',[Str]), + line(Len,LineChar). + +line(Len, Char) -> + io:format('~*c~n', [Len,Char]). + + +datetime() -> + {{YYYY,MM,DD}, {H,M,S}} = calendar:now_to_universal_time(now()), + lists:flatten(io_lib:format('~4w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w UTC',[YYYY,MM,DD, H,M,S])). + + +fmt_host_port({{A,B,C,D},Port}) -> io_lib:format('~p.~p.~p.~p:~p',[A,B,C,D,Port]); +fmt_host_port({Host,Port}) -> io_lib:format('~s:~p',[Host,Port]). + + + +nyi() -> + io:format('Not yet implemented~n',[]), + nyi. diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 35336bce8b..97e2dee27a 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2013. All Rights Reserved. +%% Copyright Ericsson AB 2005-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -73,7 +73,9 @@ read_password(Prompt, Ssh) -> listify(A) when is_atom(A) -> atom_to_list(A); listify(L) when is_list(L) -> - L. + L; +listify(B) when is_binary(B) -> + binary_to_list(B). format(Fmt, Args) -> io:format(Fmt, Args). diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 76b57cb995..66e7717095 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -162,8 +162,15 @@ encode(#ssh_msg_userauth_info_request{ encode(#ssh_msg_userauth_info_response{ num_responses = Num, data = Data}) -> - ssh_bits:encode([?SSH_MSG_USERAUTH_INFO_RESPONSE, Num, Data], - [byte, uint32, '...']); + Responses = lists:map(fun("") -> + <<>>; + (Response) -> + ssh_bits:encode([Response], [string]) + end, Data), + Start = ssh_bits:encode([?SSH_MSG_USERAUTH_INFO_RESPONSE, Num], + [byte, uint32]), + iolist_to_binary([Start, Responses]); + encode(#ssh_msg_disconnect{ code = Code, description = Desc, @@ -498,6 +505,11 @@ erl_boolean(1) -> decode_kex_init(<<?BYTE(Bool), ?UINT32(X)>>, Acc, 0) -> list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); +decode_kex_init(<<?BYTE(Bool)>>, Acc, 0) -> + %% The mandatory trailing UINT32 is missing. Assume the value it anyhow must have + %% See rfc 4253 7.1 + X = 0, + list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) -> Names = string:tokens(unicode:characters_to_list(Data), ","), decode_kex_init(Rest, [Names | Acc], N -1). diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 0ea2366ac7..721146c509 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2013. All Rights Reserved. +%% Copyright Ericsson AB 2005-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -57,7 +57,8 @@ rep_buf = <<>>, req_id, req_list = [], %% {ReqId, Fun} - inf %% list of fileinf + inf, %% list of fileinf, + opts }). -record(fileinf, @@ -85,10 +86,11 @@ start_channel(Host) when is_list(Host) -> start_channel(Host, []). start_channel(Cm, Opts) when is_pid(Cm) -> Timeout = proplists:get_value(timeout, Opts, infinity), + {_, SftpOpts} = handle_options(Opts, [], []), case ssh_xfer:attach(Cm, []) of {ok, ChannelId, Cm} -> case ssh_channel:start(Cm, ChannelId, - ?MODULE, [Cm, ChannelId, Timeout]) of + ?MODULE, [Cm, ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> @@ -108,11 +110,12 @@ start_channel(Cm, Opts) when is_pid(Cm) -> start_channel(Host, Opts) -> start_channel(Host, 22, Opts). start_channel(Host, Port, Opts) -> - Timeout = proplists:get_value(timeout, Opts, infinity), - case ssh_xfer:connect(Host, Port, proplists:delete(timeout, Opts)) of + {SshOpts, SftpOpts} = handle_options(Opts, [], []), + Timeout = proplists:get_value(timeout, SftpOpts, infinity), + case ssh_xfer:connect(Host, Port, SshOpts) of {ok, ChannelId, Cm} -> case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, - ChannelId, Timeout]) of + ChannelId, SftpOpts]) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> @@ -392,7 +395,8 @@ write_file_loop(Pid, Handle, Pos, Bin, Remain, PacketSz, FileOpTimeout) -> %% %% Description: %%-------------------------------------------------------------------- -init([Cm, ChannelId, Timeout]) -> +init([Cm, ChannelId, Options]) -> + Timeout = proplists:get_value(timeout, Options, infinity), erlang:monitor(process, Cm), case ssh_connection:subsystem(Cm, ChannelId, "sftp", Timeout) of success -> @@ -401,7 +405,8 @@ init([Cm, ChannelId, Timeout]) -> {ok, #state{xf = Xf, req_id = 0, rep_buf = <<>>, - inf = new_inf()}}; + inf = new_inf(), + opts = Options}}; failure -> {stop, "server failed to start sftp subsystem"}; Error -> @@ -707,8 +712,9 @@ handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State0) -> %% %% Description: Handles channel messages %%-------------------------------------------------------------------- -handle_msg({ssh_channel_up, _, _}, #state{xf = Xf} = State) -> - ssh_xfer:protocol_version_request(Xf), +handle_msg({ssh_channel_up, _, _}, #state{opts = Options, xf = Xf} = State) -> + Version = proplists:get_value(sftp_vsn, Options, ?SSH_SFTP_PROTOCOL_VERSION), + ssh_xfer:protocol_version_request(Xf, Version), {ok, State}; %% Version negotiation timed out @@ -754,6 +760,15 @@ terminate(_Reason, State) -> %%==================================================================== %% Internal functions %%==================================================================== +handle_options([], Sftp, Ssh) -> + {Ssh, Sftp}; +handle_options([{timeout, _} = Opt | Rest], Sftp, Ssh) -> + handle_options(Rest, [Opt | Sftp], Ssh); +handle_options([{sftp_vsn, _} = Opt| Rest], Sftp, Ssh) -> + handle_options(Rest, [Opt | Sftp], Ssh); +handle_options([Opt | Rest], Sftp, Ssh) -> + handle_options(Rest, Sftp, [Opt | Ssh]). + call(Pid, Msg, TimeOut) -> ssh_channel:call(Pid, {{timeout, TimeOut}, Msg}, infinity). diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 848133f838..660fe8bb65 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -173,8 +173,8 @@ ssh_acceptor_sup([_ | Rest]) -> ssh_acceptor_sup(Rest). stop_acceptor(Sup) -> - [Name] = - [SupName || {SupName, _, _, [ssh_acceptor_sup]} <- + [{Name, AcceptorSup}] = + [{SupName, ASup} || {SupName, ASup, _, [ssh_acceptor_sup]} <- supervisor:which_children(Sup)], - supervisor:terminate_child(Sup, Name). + supervisor:terminate_child(AcceptorSup, Name). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index ea05c849b7..76fa776113 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2013. All Rights Reserved. +%% Copyright Ericsson AB 2004-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -82,16 +82,21 @@ format_version({Major,Minor}) -> integer_to_list(Minor) ++ "-Erlang". handle_hello_version(Version) -> - StrVersion = trim_tail(Version), - case string:tokens(Version, "-") of - [_, "2.0" | _] -> - {{2,0}, StrVersion}; - [_, "1.99" | _] -> - {{2,0}, StrVersion}; - [_, "1.3" | _] -> - {{1,3}, StrVersion}; - [_, "1.5" | _] -> - {{1,5}, StrVersion} + try + StrVersion = trim_tail(Version), + case string:tokens(Version, "-") of + [_, "2.0" | _] -> + {{2,0}, StrVersion}; + [_, "1.99" | _] -> + {{2,0}, StrVersion}; + [_, "1.3" | _] -> + {{1,3}, StrVersion}; + [_, "1.5" | _] -> + {{1,5}, StrVersion} + end + catch + error:_ -> + {undefined, "unknown version"} end. key_exchange_init_msg(Ssh0) -> diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index 63d01fd9de..1881392db8 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2013. All Rights Reserved. +%% Copyright Ericsson AB 2005-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -28,7 +28,7 @@ rename/5, remove/3, mkdir/4, rmdir/3, realpath/3, extended/4, stat/4, fstat/4, lstat/4, setstat/4, readlink/3, fsetstat/4, symlink/4, - protocol_version_request/1, + protocol_version_request/2, xf_reply/2, xf_send_reply/3, xf_send_names/3, xf_send_name/4, xf_send_status/3, xf_send_status/4, xf_send_status/5, @@ -67,8 +67,8 @@ open_xfer(CM, Opts) -> Error end. -protocol_version_request(XF) -> - xf_request(XF, ?SSH_FXP_INIT, <<?UINT32(?SSH_SFTP_PROTOCOL_VERSION)>>). +protocol_version_request(XF, Version) -> + xf_request(XF, ?SSH_FXP_INIT, <<?UINT32(Version)>>). open(XF, ReqID, FileName, Access, Flags, Attrs) -> Vsn = XF#ssh_xfer.vsn, diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server.erl b/lib/ssh/test/property_test/ssh_eqc_client_server.erl index cf895ae85e..123b48412b 100644 --- a/lib/ssh/test/property_test/ssh_eqc_client_server.erl +++ b/lib/ssh/test/property_test/ssh_eqc_client_server.erl @@ -32,6 +32,10 @@ -else. +%% Limit the testing time on CI server... this needs to be improved in % from total budget. +-define(TESTINGTIME(Prop), eqc:testing_time(30,Prop)). + + -include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc_statem.hrl"). -eqc_group_commands(true). @@ -75,7 +79,9 @@ -define(SUBSYSTEMS, ["echo1", "echo2", "echo3", "echo4"]). --define(SERVER_ADDRESS, { {127,1,1,1}, inet_port({127,1,1,1}) }). +-define(SERVER_ADDRESS, { {127,1,0,choose(1,254)}, % IP + choose(1024,65535) % Port + }). -define(SERVER_EXTRA_OPTIONS, [{parallel_login,bool()}] ). @@ -97,7 +103,7 @@ %% To be called as eqc:quickcheck( ssh_eqc_client_server:prop_seq() ). prop_seq() -> - do_prop_seq(?SSH_DIR). + ?TESTINGTIME(do_prop_seq(?SSH_DIR)). %% To be called from a common_test test suite prop_seq(CT_Config) -> @@ -105,9 +111,10 @@ prop_seq(CT_Config) -> do_prop_seq(DataDir) -> - ?FORALL(Cmds,commands(?MODULE, #state{data_dir=DataDir}), + setup_rsa(DataDir), + ?FORALL(Cmds,commands(?MODULE), begin - {H,Sf,Result} = run_commands(?MODULE,Cmds), + {H,Sf,Result} = run_commands(?MODULE,Cmds,[{data_dir,DataDir}]), present_result(?MODULE, Cmds, {H,Sf,Result}, Result==ok) end). @@ -116,33 +123,35 @@ full_path(SSHdir, CT_Config) -> SSHdir). %%%---- prop_parallel() -> - do_prop_parallel(?SSH_DIR). + ?TESTINGTIME(do_prop_parallel(?SSH_DIR)). %% To be called from a common_test test suite prop_parallel(CT_Config) -> do_prop_parallel(full_path(?SSH_DIR, CT_Config)). do_prop_parallel(DataDir) -> - ?FORALL(Cmds,parallel_commands(?MODULE, #state{data_dir=DataDir}), + setup_rsa(DataDir), + ?FORALL(Cmds,parallel_commands(?MODULE), begin - {H,Sf,Result} = run_parallel_commands(?MODULE,Cmds), + {H,Sf,Result} = run_parallel_commands(?MODULE,Cmds,[{data_dir,DataDir}]), present_result(?MODULE, Cmds, {H,Sf,Result}, Result==ok) end). %%%---- prop_parallel_multi() -> - do_prop_parallel_multi(?SSH_DIR). + ?TESTINGTIME(do_prop_parallel_multi(?SSH_DIR)). %% To be called from a common_test test suite prop_parallel_multi(CT_Config) -> do_prop_parallel_multi(full_path(?SSH_DIR, CT_Config)). do_prop_parallel_multi(DataDir) -> + setup_rsa(DataDir), ?FORALL(Repetitions,?SHRINK(1,[10]), - ?FORALL(Cmds,parallel_commands(?MODULE, #state{data_dir=DataDir}), + ?FORALL(Cmds,parallel_commands(?MODULE), ?ALWAYS(Repetitions, begin - {H,Sf,Result} = run_parallel_commands(?MODULE,Cmds), + {H,Sf,Result} = run_parallel_commands(?MODULE,Cmds,[{data_dir,DataDir}]), present_result(?MODULE, Cmds, {H,Sf,Result}, Result==ok) end))). @@ -151,14 +160,12 @@ do_prop_parallel_multi(DataDir) -> %%% called when using commands/1 initial_state() -> - S = initial_state(#state{}), - S#state{initialized=true}. + #state{}. %%% called when using commands/2 -initial_state(S) -> +initial_state(DataDir) -> application:stop(ssh), - ssh:start(), - setup_rsa(S#state.data_dir). + ssh:start(). %%%---------------- weight(S, ssh_send) -> 5*length([C || C<-S#state.channels, has_subsyst(C)]); @@ -172,7 +179,7 @@ weight(_S, _) -> 1. initial_state_pre(S) -> not S#state.initialized. -initial_state_args(S) -> [S]. +initial_state_args(_) -> [{var,data_dir}]. initial_state_next(S, _, _) -> S#state{initialized=true}. @@ -180,10 +187,17 @@ initial_state_next(S, _, _) -> S#state{initialized=true}. %%% Start a new daemon %%% Precondition: not more than ?MAX_NUM_SERVERS started +%%% This is a bit funny because we need to pick an IP address and Port to +%%% run the server on, but there is no way to atomically select a free Port! +%%% +%%% Therefore we just grab one IP-Port pair randomly and try to start the ssh server +%%% on that pair. If it fails, we just forget about it and goes on. Yes, it +%%% is a waste of cpu cycles, but at least it works! + ssh_server_pre(S) -> S#state.initialized andalso length(S#state.servers) < ?MAX_NUM_SERVERS. -ssh_server_args(S) -> [?SERVER_ADDRESS, S#state.data_dir, ?SERVER_EXTRA_OPTIONS]. +ssh_server_args(_) -> [?SERVER_ADDRESS, {var,data_dir}, ?SERVER_EXTRA_OPTIONS]. ssh_server({IP,Port}, DataDir, ExtraOptions) -> ok(ssh:daemon(IP, Port, @@ -194,8 +208,10 @@ ssh_server({IP,Port}, DataDir, ExtraOptions) -> | ExtraOptions ])). +ssh_server_post(_S, _Args, {error,eaddrinuse}) -> true; ssh_server_post(_S, _Args, Result) -> is_ok(Result). +ssh_server_next(S, {error,eaddrinuse}, _) -> S; ssh_server_next(S, Result, [{IP,Port},_,_]) -> S#state{servers=[#srvr{ref = Result, address = IP, @@ -241,15 +257,16 @@ do(Pid, Fun, Timeout) when is_function(Fun,0) -> ssh_open_connection_pre(S) -> S#state.servers /= []. -ssh_open_connection_args(S) -> [oneof(S#state.servers), S#state.data_dir]. +ssh_open_connection_args(S) -> [oneof(S#state.servers), {var,data_dir}]. ssh_open_connection(#srvr{address=Ip, port=Port}, DataDir) -> ok(ssh:connect(ensure_string(Ip), Port, [ {silently_accept_hosts, true}, {user_dir, user_dir(DataDir)}, - {user_interaction, false} - ])). + {user_interaction, false}, + {connect_timeout, 2000} + ], 2000)). ssh_open_connection_post(_S, _Args, Result) -> is_ok(Result). @@ -569,12 +586,6 @@ median(_) -> %%%================================================================ %%% The rest is taken and modified from ssh_test_lib.erl -inet_port(IpAddress)-> - {ok, Socket} = gen_tcp:listen(0, [{ip,IpAddress},{reuseaddr,true}]), - {ok, Port} = inet:port(Socket), - gen_tcp:close(Socket), - Port. - setup_rsa(Dir) -> erase_dir(system_dir(Dir)), erase_dir(user_dir(Dir)), diff --git a/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl b/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl index 34630bdc91..57ea2012c1 100644 --- a/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl +++ b/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl @@ -25,8 +25,6 @@ -proptest(eqc). -proptest([triq,proper]). --include_lib("ct_property_test.hrl"). - -ifndef(EQC). -ifndef(PROPER). -ifndef(TRIQ). diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 9242731924..415cb9fc9c 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -798,12 +798,14 @@ ssh_connect_nonegtimeout_connected(Config, Parallel) -> {parallel_login, Parallel}, {negotiation_timeout, NegTimeOut}, {failfun, fun ssh_test_lib:failfun/2}]), + ct:pal("~p Listen ~p:~p",[_Pid,_Host,Port]), ct:sleep(500), IO = ssh_test_lib:start_io_server(), Shell = ssh_test_lib:start_shell(Port, IO, UserDir), receive - {'EXIT', _, _} -> + Error = {'EXIT', _, _} -> + ct:pal("~p",[Error]), ct:fail(no_ssh_connection); ErlShellStart -> ct:pal("---Erlang shell start: ~p~n", [ErlShellStart]), @@ -898,7 +900,12 @@ connect_fun(ssh_sftp__start_channel, _Config) -> end. -max_sessions(Config, ParallelLogin, Connect) when is_function(Connect,2) -> +max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> + Connect = fun(Host,Port) -> + R = Connect0(Host,Port), + ct:pal("Connect(~p,~p) -> ~p",[Host,Port,R]), + R + end, SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), MaxSessions = 5, @@ -909,7 +916,7 @@ max_sessions(Config, ParallelLogin, Connect) when is_function(Connect,2) -> {parallel_login, ParallelLogin}, {max_sessions, MaxSessions} ]), - + ct:pal("~p Listen ~p:~p for max ~p sessions",[Pid,Host,Port,MaxSessions]), try [Connect(Host,Port) || _ <- lists:seq(1,MaxSessions)] of Connections -> diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl index c115ccee5f..553d0f5720 100644 --- a/lib/ssh/test/ssh_connection_SUITE.erl +++ b/lib/ssh/test/ssh_connection_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -31,23 +31,37 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> - [{ct_hooks,[ts_install_cth]}]. +%% suite() -> +%% [{ct_hooks,[ts_install_cth]}]. all() -> [ - {group, openssh_payload}, + {group, openssh}, + start_subsystem_on_closed_channel, interrupted_send, start_shell, start_shell_exec, - start_shell_exec_fun + start_shell_exec_fun, + gracefull_invalid_version, + gracefull_invalid_start, + gracefull_invalid_long_start, + gracefull_invalid_long_start_no_nl, + stop_listener ]. groups() -> - [{openssh_payload, [], [simple_exec, - small_cat, - big_cat, - send_after_exit - ]}]. + [{openssh, [], payload() ++ ptty()}]. + +payload() -> + [simple_exec, + small_cat, + big_cat, + send_after_exit]. + +ptty() -> + [ptty_alloc_default, + ptty_alloc, + ptty_alloc_pixel]. + %%-------------------------------------------------------------------- init_per_suite(Config) -> case catch crypto:start() of @@ -61,13 +75,13 @@ end_per_suite(_Config) -> crypto:stop(). %%-------------------------------------------------------------------- -init_per_group(openssh_payload, _Config) -> +init_per_group(openssh, _Config) -> case gen_tcp:connect("localhost", 22, []) of {error,econnrefused} -> {skip,"No openssh deamon"}; {ok, Socket} -> gen_tcp:close(Socket) - end; + end; init_per_group(_, Config) -> Config. @@ -180,10 +194,10 @@ big_cat(Config) when is_list(Config) -> case size(Data) =:= size(Other) of true -> ct:pal("received and sent data are same" - "size but do not match~n",[]); + "size but do not match~n",[]); false -> ct:pal("sent ~p but only received ~p~n", - [size(Data), size(Other)]) + [size(Data), size(Other)]) end, ct:fail(receive_data_mismatch); Else -> @@ -236,6 +250,68 @@ send_after_exit(Config) when is_list(Config) -> end. %%-------------------------------------------------------------------- +ptty_alloc_default() -> + [{doc, "Test sending PTTY alloc message with only defaults."}]. + +ptty_alloc_default(Config) when is_list(Config) -> + ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user_interaction, false}]), + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []), + ssh:close(ConnectionRef). + +%%-------------------------------------------------------------------- +ptty_alloc() -> + [{doc, "Test sending PTTY alloc message with width,height options."}]. + +ptty_alloc(Config) when is_list(Config) -> + ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user_interaction, false}]), + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, + [{term, default_term()}, {width, 70}, {high, 20}]), + ssh:close(ConnectionRef). + + +%%-------------------------------------------------------------------- +ptty_alloc_pixel() -> + [{doc, "Test sending PTTY alloc message pixel options."}]. + +ptty_alloc_pixel(Config) when is_list(Config) -> + ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user_interaction, false}]), + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, + [{term, default_term()}, {pixel_widh, 630}, {pixel_hight, 470}]), + ssh:close(ConnectionRef). + +%%-------------------------------------------------------------------- +start_subsystem_on_closed_channel(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}, + {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}]), + + ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), + + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + + ok = ssh_connection:close(ConnectionRef, ChannelId), + + failure = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity), + + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- interrupted_send() -> [{doc, "Use a subsystem that echos n char and then sends eof to cause a channel exit partway through a large send."}]. @@ -250,10 +326,10 @@ interrupted_send(Config) when is_list(Config) -> {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}]), ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), + {user, "foo"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), @@ -288,24 +364,24 @@ start_shell(Config) when is_list(Config) -> file:make_dir(UserDir), SysDir = ?config(data_dir, Config), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {shell, fun(U, H) -> start_our_shell(U, H) end} ]), + {user_dir, UserDir}, + {password, "morot"}, + {shell, fun(U, H) -> start_our_shell(U, H) end} ]), ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, true}, - {user_dir, UserDir}]), + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), ok = ssh_connection:shell(ConnectionRef,ChannelId0), receive - {ssh_cm,ConnectionRef, {data, ChannelId, 0, <<"Enter command\r\n">>}} -> - ok + {ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"Enter command\r\n">>}} -> + ok after 5000 -> - ct:fail("CLI Timeout") + ct:fail("CLI Timeout") end, ssh:close(ConnectionRef), @@ -320,25 +396,25 @@ start_shell_exec(Config) when is_list(Config) -> file:make_dir(UserDir), SysDir = ?config(data_dir, Config), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {exec, {?MODULE,ssh_exec,[]}} ]), + {user_dir, UserDir}, + {password, "morot"}, + {exec, {?MODULE,ssh_exec,[]}} ]), ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, true}, - {user_dir, UserDir}]), + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), success = ssh_connection:exec(ConnectionRef, ChannelId0, - "testing", infinity), + "testing", infinity), receive - {ssh_cm,ConnectionRef, {data, ChannelId, 0, <<"testing\r\n">>}} -> - ok + {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"testing\r\n">>}} -> + ok after 5000 -> - ct:fail("Exec Timeout") + ct:fail("Exec Timeout") end, ssh:close(ConnectionRef), @@ -354,30 +430,177 @@ start_shell_exec_fun(Config) when is_list(Config) -> file:make_dir(UserDir), SysDir = ?config(data_dir, Config), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {exec, fun ssh_exec/1}]), + {user_dir, UserDir}, + {password, "morot"}, + {exec, fun ssh_exec/1}]), ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, true}, - {user_dir, UserDir}]), + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), success = ssh_connection:exec(ConnectionRef, ChannelId0, - "testing", infinity), + "testing", infinity), receive - {ssh_cm,ConnectionRef, {data, ChannelId, 0, <<"testing\r\n">>}} -> - ok + {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"testing\r\n">>}} -> + ok after 5000 -> - ct:fail("Exec Timeout") + ct:fail("Exec Timeout") end, ssh:close(ConnectionRef), ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- + +gracefull_invalid_version(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + + {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}]), + + {ok, S} = gen_tcp:connect(Host, Port, []), + ok = gen_tcp:send(S, ["SSH-8.-1","\r\n"]), + receive + Verstring -> + ct:pal("Server version: ~p~n", [Verstring]), + receive + {tcp_closed, S} -> + ok + end + end. + +gracefull_invalid_start(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}]), + + {ok, S} = gen_tcp:connect(Host, Port, []), + ok = gen_tcp:send(S, ["foobar","\r\n"]), + receive + Verstring -> + ct:pal("Server version: ~p~n", [Verstring]), + receive + {tcp_closed, S} -> + ok + end + end. + +gracefull_invalid_long_start(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}]), + + {ok, S} = gen_tcp:connect(Host, Port, []), + ok = gen_tcp:send(S, [lists:duplicate(257, $a), "\r\n"]), + receive + Verstring -> + ct:pal("Server version: ~p~n", [Verstring]), + receive + {tcp_closed, S} -> + ok + end + end. + + +gracefull_invalid_long_start_no_nl(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}]), + + {ok, S} = gen_tcp:connect(Host, Port, []), + ok = gen_tcp:send(S, [lists:duplicate(257, $a), "\r\n"]), + receive + Verstring -> + ct:pal("Server version: ~p~n", [Verstring]), + receive + {tcp_closed, S} -> + ok + end + end. + +stop_listener() -> + [{doc, "start ssh daemon, setup connections, stop listener, restart listner"}]. + +stop_listener(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = ?config(data_dir, Config), + + {Pid0, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}, + {exec, fun ssh_exec/1}]), + + ConnectionRef0 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), + + {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef0, infinity), + + ssh:stop_listener(Host, Port), + + {error, _} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), + success = ssh_connection:exec(ConnectionRef0, ChannelId0, + "testing", infinity), + receive + {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"testing\r\n">>}} -> + ok + after 5000 -> + ct:fail("Exec Timeout") + end, + + {ok, HostAddr} = inet:getaddr(Host, inet), + case ssh_test_lib:daemon(HostAddr, Port, [{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "potatis"}, + {exec, fun ssh_exec/1}]) of + {Pid1, HostAddr, Port} -> + ConnectionRef1 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "potatis"}, + {user_interaction, true}, + {user_dir, UserDir}]), + {error, _} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), + ssh:close(ConnectionRef0), + ssh:close(ConnectionRef1), + ssh:stop_daemon(Pid0), + ssh:stop_daemon(Pid1); + Error -> + ct:fail({unexpected, Error}) + end. + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -413,14 +636,22 @@ collect_data(ConnectionRef, ChannelId, Acc) -> end. %%%------------------------------------------------------------------- -% This is taken from the ssh example code. +%% This is taken from the ssh example code. start_our_shell(_User, _Peer) -> spawn(fun() -> - io:format("Enter command\n") - %% Don't actually loop, just exit + io:format("Enter command\n") + %% Don't actually loop, just exit end). ssh_exec(Cmd) -> spawn(fun() -> - io:format(Cmd ++ "\n") + io:format(Cmd ++ "\n") end). + +default_term() -> + case os:getenv("TERM") of + false -> + "vt100"; + Str when is_list(Str)-> + Str + end. diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index 56b1363b7a..4c46a1b1a8 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2013. All Rights Reserved. +%% Copyright Ericsson AB 2005-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -65,7 +65,7 @@ groups() -> [{erlang_server, [], [open_close_file, open_close_dir, read_file, read_dir, write_file, rename_file, mk_rm_dir, remove_file, links, retrieve_attributes, set_attributes, async_read, - async_write, position, pos_read, pos_write]}, + async_write, position, pos_read, pos_write, version_option]}, {openssh_server, [], [open_close_file, open_close_dir, read_file, read_dir, write_file, rename_file, mk_rm_dir, remove_file, links, retrieve_attributes, set_attributes, async_read, @@ -111,6 +111,21 @@ init_per_testcase(sftp_nonexistent_subsystem, Config) -> ]), [{sftpd, Sftpd} | Config]; +init_per_testcase(version_option, Config) -> + prep(Config), + TmpConfig0 = lists:keydelete(watchdog, 1, Config), + TmpConfig = lists:keydelete(sftp, 1, TmpConfig0), + Dog = ct:timetrap(?default_timeout), + {_,Host, Port} = ?config(sftpd, Config), + {ok, ChannelPid, Connection} = + ssh_sftp:start_channel(Host, Port, + [{sftp_vsn, 3}, + {user, ?USER}, + {password, ?PASSWD}, + {user_interaction, false}, + {silently_accept_hosts, true}]), + Sftp = {ChannelPid, Connection}, + [{sftp, Sftp}, {watchdog, Dog} | TmpConfig]; init_per_testcase(Case, Config) -> prep(Config), TmpConfig0 = lists:keydelete(watchdog, 1, Config), @@ -447,6 +462,11 @@ sftp_nonexistent_subsystem(Config) when is_list(Config) -> {silently_accept_hosts, true}]). %%-------------------------------------------------------------------- +version_option() -> + [{doc, "Test API option sftp_vsn"}]. +version_option(Config) when is_list(Config) -> + open_close_dir(Config). +%%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- prep(Config) -> diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index 00c25bf394..b8abf5e80e 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -113,6 +113,9 @@ io_request({put_chars, Chars}, TestCase, _, _, Buff) -> io_request({put_chars, unicode, Chars}, TestCase, _, _, Buff) when is_binary(Chars) -> reply(TestCase, Chars), {ok, ok, Buff}; +io_request({put_chars, unicode, io_lib, format, [Fmt,Args]}, TestCase, _, _, Buff) -> + reply(TestCase, io_lib:format(Fmt,Args)), + {ok, ok, Buff}; io_request({put_chars, Enc, Chars}, TestCase, _, _, Buff) -> reply(TestCase, unicode:characters_to_binary(Chars,Enc,latin1)), {ok, ok, Buff}; diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl index 3500bf012b..af70eeb46c 100644 --- a/lib/ssh/test/ssh_to_openssh_SUITE.erl +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -119,22 +119,9 @@ erlang_shell_client_openssh_server(Config) when is_list(Config) -> IO ! {input, self(), "echo Hej\n"}, receive_hej(), IO ! {input, self(), "exit\n"}, - receive - <<"logout">> -> - receive - <<"Connection closed">> -> - ok - end; - Other0 -> - ct:fail({unexpected_msg, Other0}) - end, - receive - {'EXIT', Shell, normal} -> - ok; - Other1 -> - ct:fail({unexpected_msg, Other1}) - end. - + receive_logout(), + receive_normal_exit(Shell). + %-------------------------------------------------------------------- erlang_client_openssh_server_exec() -> [{doc, "Test api function ssh_connection:exec"}]. @@ -537,11 +524,44 @@ erlang_client_openssh_server_nonexistent_subsystem(Config) when is_list(Config) %%-------------------------------------------------------------------- receive_hej() -> receive - <<"Hej\n">> = Hej-> + <<"Hej", _binary>> = Hej -> + ct:pal("Expected result: ~p~n", [Hej]); + <<"Hej\n", _binary>> = Hej -> + ct:pal("Expected result: ~p~n", [Hej]); + <<"Hej\r\n", _/binary>> = Hej -> ct:pal("Expected result: ~p~n", [Hej]); Info -> - ct:pal("Extra info: ~p~n", [Info]), - receive_hej() + Lines = binary:split(Info, [<<"\r\n">>], [global]), + case lists:member(<<"Hej">>, Lines) of + true -> + ct:pal("Expected result found in lines: ~p~n", [Lines]), + ok; + false -> + ct:pal("Extra info: ~p~n", [Info]), + receive_hej() + end + end. + +receive_logout() -> + receive + <<"logout">> -> + receive + <<"Connection closed">> -> + ok + end; + Info -> + ct:pal("Extra info when logging out: ~p~n", [Info]), + receive_logout() + end. + +receive_normal_exit(Shell) -> + receive + {'EXIT', Shell, normal} -> + ok; + <<"\r\n">> -> + receive_normal_exit(Shell); + Other -> + ct:fail({unexpected_msg, Other}) end. %%-------------------------------------------------------------------- @@ -564,4 +584,7 @@ check_ssh_client_support2(P) -> check_ssh_client_support2(P); {P, {exit_status, E}} -> E + after 5000 -> + ct:pal("Openssh command timed out ~n"), + -1 end. diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 73bf73971f..68544c1d0e 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 3.0.5 +SSH_VSN = 3.0.8 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 1b37a2baa2..62e9bd0165 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -25,7 +25,63 @@ <file>notes.xml</file> </header> <p>This document describes the changes made to the SSL application.</p> - <section><title>SSL 5.3.5</title> + <section><title>SSL 5.3.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handle the fact that servers may send an empty SNI + extension to the client.</p> + <p> + Own Id: OTP-12198</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Corrected handling of ECC certificates, there where + several small issues with the handling of such + certificates in the ssl and public_key application. Now + ECC signed ECC certificates shall work and not only RSA + signed ECC certificates.</p> + <p> + Own Id: OTP-12026</p> + </item> + <item> + <p> + Check that the certificate chain ends with a trusted ROOT + CA e.i. a self-signed certificate, but provide an option + partial_chain to enable the application to define an + intermediat CA as trusted.</p> + <p> + Own Id: OTP-12149</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add decode functions for SNI (Server Name Indication)</p> + <p> + Own Id: OTP-12048</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.5</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index ffee4bd1af..b53344e381 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -226,7 +226,7 @@ <p>The verification fun should be defined as:</p> <code> -fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | +fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() | {revoked, atom()}} | {extension, #'Extension'{}}, InitialUserState :: term()) -> {valid, UserState :: term()} | {valid_peer, UserState :: term()} | {fail, Reason :: term()} | {unknown, UserState :: term()}. @@ -252,7 +252,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | always returns {valid, UserState}, the TLS/SSL handshake will not be terminated with respect to verification failures and the connection will be established. If called with an - extension unknown to the user application the return value + extension unknown to the user application, the return value {unknown, UserState} should be used.</p> <p>The default verify_fun option in verify_peer mode:</p> @@ -283,9 +283,29 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | end, []} </code> -<p>Possible path validation errors: </p> + <p>Possible path validation errors are given on the form {bad_cert, Reason} where Reason is:</p> -<p> {bad_cert, cert_expired}, {bad_cert, invalid_issuer}, {bad_cert, invalid_signature}, {bad_cert, unknown_ca},{bad_cert, selfsigned_peer}, {bad_cert, name_not_permitted}, {bad_cert, missing_basic_constraint}, {bad_cert, invalid_key_usage}</p> + <taglist> + <tag>unknown_ca</tag> + <item>No trusted CA was found in the trusted store. The trusted CA is + normally a so called ROOT CA that is a self-signed cert. Trust may + be claimed for an intermediat CA (trusted anchor does not have to be self signed + according to X-509) by using the option <c>partial_chain</c></item> + + <tag>selfsigned_peer</tag> + <item>The chain consisted only of one self-signed certificate.</item> + + <tag>PKIX X-509-path validation error</tag> + <item> Possible such reasons see <seealso + marker="public_key:public_key#pkix_path_validation-3"> public_key:pkix_path_validation/3 </seealso></item> + </taglist> + + </item> + + <tag>{partial_chain, fun(Chain::[DerCert]) -> {trusted_ca, DerCert} | unknown_ca </tag> + <item> + Claim an intermediat CA in the chain as trusted. TLS will then perform the public_key:pkix_path_validation/3 + with the selected CA as trusted anchor and the rest of the chain. </item> <tag>{versions, [protocol()]}</tag> diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 7c4c8ec2cc..0c00a650b9 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -118,7 +118,7 @@ $(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) clean: - rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES) rm -f errs core *~ $(APP_TARGET): $(APP_SRC) ../vsn.mk diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index b713f86c1e..9d692379b4 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,12 +1,24 @@ %% -*- erlang -*- {"%VSN%", [ + {"5.3.6", [{load_module, ssl_handshake, soft_purge, soft_purge, [ssl_connection]}]}, + {"5.3.5", [{load_module, ssl, soft_purge, soft_purge, [ssl_connection]}, + {load_module, ssl_handshake, soft_purge, soft_purge, [ssl_certificate]}, + {load_module, ssl_certificate, soft_purge, soft_purge, []}, + {load_module, ssl_connection, soft_purge, soft_purge, [tls_connection]}, + {update, tls_connection, {advanced, {up, "5.3.5", "5.3.6"}}, [ssl_handshake]}]}, {<<"5\\.3\\.[1-4]($|\\..*)">>, [{restart_application, ssl}]}, {<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, {<<"3\\..*">>, [{restart_application, ssl}]} ], [ + {"5.3.6", [{load_module, ssl_handshake, soft_purge, soft_purge, [ssl_connection]}]}, + {"5.3.5", [{load_module, ssl, soft_purge, soft_purge,[ssl_certificate]}, + {load_module, ssl_handshake, soft_purge, soft_purge,[ssl_certificate]}, + {load_module, ssl_certificate, soft_purge, soft_purge,[]}, + {load_module, ssl_connection, soft_purge, soft_purge,[tls_connection]}, + {update, tls_connection, {advanced, {down, "5.3.6", "5.3.5"}}, [ssl_handshake]}]}, {<<"5\\.3\\.[1-4]($|\\..*)">>, [{restart_application, ssl}]}, {<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index d741fa63fb..b4bea25942 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -569,21 +569,24 @@ handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, cacertfile = CaCertFile0} = InheritedSslOpts) -> RecordCB = record_cb(Protocol), CaCerts = handle_option(cacerts, Opts0, CaCerts0), - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} = handle_verify_options(Opts0, CaCerts), + {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder} = handle_verify_options(Opts0, CaCerts), CaCertFile = case proplists:get_value(cacertfile, Opts0, CaCertFile0) of undefined -> CaCertDefault; CAFile -> CAFile end, + NewVerifyOpts = InheritedSslOpts#ssl_options{cacerts = CaCerts, cacertfile = CaCertFile, verify = Verify, verify_fun = VerifyFun, + partial_chain = PartialChainHanlder, fail_if_no_peer_cert = FailIfNoPeerCert}, SslOpts1 = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) - end, Opts0, [cacerts, cacertfile, verify, verify_fun, fail_if_no_peer_cert]), + end, Opts0, [cacerts, cacertfile, verify, verify_fun, partial_chain, + fail_if_no_peer_cert]), case handle_option(versions, SslOpts1, []) of [] -> new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB); @@ -603,10 +606,10 @@ handle_options(Opts0) -> ReuseSessionFun = fun(_, _, _, _) -> true end, CaCerts = handle_option(cacerts, Opts, undefined), - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} = handle_verify_options(Opts, CaCerts), + {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder} = + handle_verify_options(Opts, CaCerts), CertFile = handle_option(certfile, Opts, <<>>), - RecordCb = record_cb(Opts), Versions = case handle_option(versions, Opts, []) of @@ -620,6 +623,7 @@ handle_options(Opts0) -> versions = Versions, verify = validate_option(verify, Verify), verify_fun = VerifyFun, + partial_chain = PartialChainHanlder, fail_if_no_peer_cert = FailIfNoPeerCert, verify_client_once = handle_option(verify_client_once, Opts, false), depth = handle_option(depth, Opts, 1), @@ -656,7 +660,7 @@ handle_options(Opts0) -> }, CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), - SslOptions = [protocol, versions, verify, verify_fun, + SslOptions = [protocol, versions, verify, verify_fun, partial_chain, fail_if_no_peer_cert, verify_client_once, depth, cert, certfile, key, keyfile, password, cacerts, cacertfile, dh, dhfile, @@ -708,6 +712,8 @@ validate_option(verify_fun, Fun) when is_function(Fun) -> end, Fun}; validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) -> Value; +validate_option(partial_chain, Value) when is_function(Value) -> + Value; validate_option(fail_if_no_peer_cert, Value) when is_boolean(Value) -> Value; validate_option(verify_client_once, Value) when is_boolean(Value) -> @@ -1147,25 +1153,32 @@ handle_verify_options(Opts, CaCerts) -> UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false), UserVerifyFun = handle_option(verify_fun, Opts, undefined), - + PartialChainHanlder = handle_option(partial_chain, Opts, + fun(_) -> unknown_ca end), + %% Handle 0, 1, 2 for backwards compatibility case proplists:get_value(verify, Opts, verify_none) of 0 -> {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun}; + ca_cert_default(verify_none, VerifyNoneFun, CaCerts), + VerifyNoneFun, PartialChainHanlder}; 1 -> {verify_peer, false, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; + ca_cert_default(verify_peer, UserVerifyFun, CaCerts), + UserVerifyFun, PartialChainHanlder}; 2 -> {verify_peer, true, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; - verify_none -> + ca_cert_default(verify_peer, UserVerifyFun, CaCerts), + UserVerifyFun, PartialChainHanlder}; + verify_none -> {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun}; + ca_cert_default(verify_none, VerifyNoneFun, CaCerts), + VerifyNoneFun, PartialChainHanlder}; verify_peer -> {verify_peer, UserFailIfNoPeerCert, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; + ca_cert_default(verify_peer, UserVerifyFun, CaCerts), + UserVerifyFun, PartialChainHanlder}; Value -> throw({error, {options, {verify, Value}}}) end. diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 53366b060c..9c0ed181fe 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -30,7 +30,7 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([trusted_cert_and_path/3, +-export([trusted_cert_and_path/4, certificate_chain/3, file_to_certificats/2, validate_extension/3, @@ -46,14 +46,14 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref()) -> +-spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref(), fun()) -> {der_cert() | unknown_ca, [der_cert()]}. %% %% Description: Extracts the root cert (if not presents tries to %% look it up, if not found {bad_cert, unknown_ca} will be added verification %% errors. Returns {RootCert, Path, VerifyErrors} %%-------------------------------------------------------------------- -trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef) -> +trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -> Path = [Cert | _] = lists:reverse(CertChain), OtpCert = public_key:pkix_decode_cert(Cert, otp), SignedAndIssuerID = @@ -62,32 +62,23 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef) -> {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self), {self, IssuerId}; false -> - case public_key:pkix_issuer_id(OtpCert, other) of - {ok, IssuerId} -> - {other, IssuerId}; - {error, issuer_not_found} -> - case find_issuer(OtpCert, CertDbHandle) of - {ok, IssuerId} -> - {other, IssuerId}; - Other -> - Other - end - end + other_issuer(OtpCert, CertDbHandle) end, case SignedAndIssuerID of {error, issuer_not_found} -> %% The root CA was not sent and can not be found. - {unknown_ca, Path}; + handle_incomplete_chain(Path, PartialChainHandler); {self, _} when length(Path) == 1 -> {selfsigned_peer, Path}; {_ ,{SerialNr, Issuer}} -> case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, Issuer) of - {ok, {BinCert,_}} -> - {BinCert, Path}; + {ok, Trusted} -> + %% Trusted must be selfsigned or it is an incomplete chain + handle_path(Trusted, Path, PartialChainHandler); _ -> %% Root CA could not be verified - {unknown_ca, Path} + handle_incomplete_chain(Path, PartialChainHandler) end end. @@ -222,28 +213,27 @@ certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned _ -> %% The trusted cert may be obmitted from the chain as the %% counter part needs to have it anyway to be able to - %% verify it. This will be the normal case for servers - %% that does not verify the clients and hence have not - %% specified the cacertfile. + %% verify it. {ok, lists:reverse(Chain)} end. find_issuer(OtpCert, CertDbHandle) -> - IsIssuerFun = fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> - case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of - true -> - case verify_cert_signer(OtpCert, ErlCertCandidate#'OTPCertificate'.tbsCertificate) of - true -> - throw(public_key:pkix_issuer_id(ErlCertCandidate, self)); - false -> - Acc - end; - false -> - Acc - end; - (_, Acc) -> - Acc - end, + IsIssuerFun = + fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> + case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of + true -> + case verify_cert_signer(OtpCert, ErlCertCandidate#'OTPCertificate'.tbsCertificate) of + true -> + throw(public_key:pkix_issuer_id(ErlCertCandidate, self)); + false -> + Acc + end; + false -> + Acc + end; + (_, Acc) -> + Acc + end, try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of issuer_not_found -> @@ -275,3 +265,41 @@ public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorith parameters = {params, Params}}, subjectPublicKey = Key}) -> {Key, Params}. + +other_issuer(OtpCert, CertDbHandle) -> + case public_key:pkix_issuer_id(OtpCert, other) of + {ok, IssuerId} -> + {other, IssuerId}; + {error, issuer_not_found} -> + case find_issuer(OtpCert, CertDbHandle) of + {ok, IssuerId} -> + {other, IssuerId}; + Other -> + Other + end + end. + +handle_path({BinCert, OTPCert}, Path, PartialChainHandler) -> + case public_key:pkix_is_self_signed(OTPCert) of + true -> + {BinCert, Path}; + false -> + handle_incomplete_chain(Path, PartialChainHandler) + end. + +handle_incomplete_chain(Chain, Fun) -> + case catch Fun(Chain) of + {trusted_ca, DerCert} -> + new_trusteded_chain(DerCert, Chain); + unknown_ca = Error -> + {Error, Chain}; + _ -> + {unknown_ca, Chain} + end. + +new_trusteded_chain(DerCert, [DerCert | Chain]) -> + {DerCert, Chain}; +new_trusteded_chain(DerCert, [_ | Rest]) -> + new_trusteded_chain(DerCert, Rest); +new_trusteded_chain(_, []) -> + unknown_ca. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 4ac4e81d9e..8ff9913cee 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -414,7 +414,9 @@ certify(#certificate{} = Cert, ssl_options = Opts} = State, Connection) -> case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth, Opts#ssl_options.verify, - Opts#ssl_options.verify_fun, Role) of + Opts#ssl_options.verify_fun, + Opts#ssl_options.partial_chain, + Role) of {PeerCert, PublicKeyInfo} -> handle_peer_cert(Role, PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}, Connection); diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 94ffd180c5..88ccb94e0b 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -49,7 +49,7 @@ finished/5, next_protocol/1]). %% Handle handshake messages --export([certify/7, client_certificate_verify/6, certificate_verify/6, verify_signature/5, +-export([certify/8, client_certificate_verify/6, certificate_verify/6, verify_signature/5, master_secret/5, server_key_exchange_hash/2, verify_connection/6, init_handshake_history/0, update_handshake_history/2, verify_server_key/5 ]). @@ -383,13 +383,13 @@ verify_signature(_Version, Hash, {HashAlgo, ecdsa}, Signature, %%-------------------------------------------------------------------- -spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, - verify_peer | verify_none, {fun(), term}, + verify_peer | verify_none, {fun(), term}, fun(), client | server) -> {der_cert(), public_key_info()} | #alert{}. %% %% Description: Handles a certificate handshake message %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, - MaxPathLen, _Verify, VerifyFunAndState, Role) -> + MaxPathLen, _Verify, VerifyFunAndState, PartialChain, Role) -> [PeerCert | _] = ASN1Certs, ValidationFunAndState = @@ -421,7 +421,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, try {TrustedErlCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef), + ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, PartialChain), case public_key:pkix_path_validation(TrustedErlCert, CertPath, [{max_path_length, @@ -1732,6 +1732,9 @@ dec_hello_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len), #ec_point_formats{ec_point_format_list = ECPointFormats}}); +dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), Rest/binary>>, Acc) when Len == 0 -> + dec_hello_extensions(Rest, Acc#hello_extensions{sni = ""}); %% Server may send an empy SNI + dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), ExtData:Len/binary, Rest/binary>>, Acc) -> <<?UINT16(_), NameList/binary>> = ExtData, diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index fd0d87bd5f..85724de4bd 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -74,6 +74,7 @@ versions :: [ssl_record:ssl_version()], %% ssl_record:atom_version() in API verify :: verify_none | verify_peer, verify_fun, %%:: fun(CertVerifyErrors::term()) -> boolean(), + partial_chain :: fun(), fail_if_no_peer_cert :: boolean(), verify_client_once :: boolean(), %% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError} diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 26de51985a..7df73fb581 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -329,7 +329,10 @@ terminate(Reason, StateName, State) -> %% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- -code_change(_OldVsn, StateName, State, _Extra) -> +code_change(_OldVsn, StateName, State0, {Direction, From, To}) -> + State = convert_state(State0, Direction, From, To), + {ok, StateName, State}; +code_change(_OldVsn, StateName, State, _) -> {ok, StateName, State}. format_status(Type, Data) -> @@ -958,3 +961,14 @@ workaround_transport_delivery_problems(Socket, gen_tcp = Transport) -> Transport:recv(Socket, 0, 30000); workaround_transport_delivery_problems(Socket, Transport) -> Transport:close(Socket). + +convert_state(#state{ssl_options = Options} = State, up, "5.3.5", "5.3.6") -> + State#state{ssl_options = convert_options_partial_chain(Options, up)}; +convert_state(#state{ssl_options = Options} = State, down, "5.3.6", "5.3.5") -> + State#state{ssl_options = convert_options_partial_chain(Options, down)}. + +convert_options_partial_chain(Options, up) -> + {Head, Tail} = lists:split(5, tuple_to_list(Options)), + list_to_tuple(Head ++ [{partial_chain, fun(_) -> unknown_ca end}] ++ Tail); +convert_options_partial_chain(Options, down) -> + list_to_tuple(proplists:delete(partial_chain, tuple_to_list(Options))). diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index 77e4c80bbe..3566a8a0a5 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -163,11 +163,11 @@ client_ecdh_server_ecdh(Config) when is_list(Config) -> client_ecdh_server_rsa(Config) when is_list(Config) -> COpts = ?config(client_ecdh_rsa_opts, Config), - SOpts = ?config(server_verification_opts, Config), + SOpts = ?config(server_ecdh_rsa_verify_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_ecdh(Config) when is_list(Config) -> - COpts = ?config(client_verification_opts, Config), + COpts = ?config(client_ecdh_rsa_opts, Config), SOpts = ?config(server_ecdh_rsa_verify_opts, Config), basic_test(COpts, SOpts, Config). @@ -183,11 +183,11 @@ client_ecdsa_server_ecdsa(Config) when is_list(Config) -> client_ecdsa_server_rsa(Config) when is_list(Config) -> COpts = ?config(client_ecdsa_opts, Config), - SOpts = ?config(server_verification_opts, Config), + SOpts = ?config(server_ecdsa_verify_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_ecdsa(Config) when is_list(Config) -> - COpts = ?config(client_verification_opts, Config), + COpts = ?config(client_ecdsa_opts, Config), SOpts = ?config(server_ecdsa_verify_opts, Config), basic_test(COpts, SOpts, Config). diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index 14047c6e9c..b7864ba6e7 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012-2013. All Rights Reserved. +%% Copyright Ericsson AB 2012-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -58,6 +58,10 @@ tests() -> server_verify_none, server_require_peer_cert_ok, server_require_peer_cert_fail, + server_require_peer_cert_partial_chain, + server_require_peer_cert_allow_partial_chain, + server_require_peer_cert_do_not_allow_partial_chain, + server_require_peer_cert_partial_chain_fun_fail, verify_fun_always_run_client, verify_fun_always_run_server, cert_expired, @@ -143,8 +147,8 @@ server_verify_none() -> [{doc,"Test server option verify_none"}]. server_verify_none(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), Active = ?config(active, Config), ReceiveFunction = ?config(receive_function, Config), @@ -261,6 +265,163 @@ server_require_peer_cert_fail(Config) when is_list(Config) -> end. %%-------------------------------------------------------------------- + +server_require_peer_cert_partial_chain() -> + [{doc, "Client sends an incompleate chain, by default not acceptable."}]. + +server_require_peer_cert_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ?config(server_verification_opts, Config)], + ClientOpts = ?config(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), + [{_,RootCA,_}, {_, _, _}] = public_key:pem_decode(ClientCAs), + + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false}, + {cacerts, [RootCA]} | + proplists:delete(cacertfile, ClientOpts)]}]), + receive + {Server, {error, {tls_alert, "unknown ca"}}} -> + receive + {Client, {error, {tls_alert, "unknown ca"}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. +%%-------------------------------------------------------------------- +server_require_peer_cert_allow_partial_chain() -> + [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. + +server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ?config(server_verification_opts, Config)], + ClientOpts = ?config(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(CertChain) -> + case lists:member(IntermidiateCA, CertChain) of + true -> + {trusted_ca, IntermidiateCA}; + false -> + unknown_ca + end + end, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts)]}]), + 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, ClientOpts}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + %%-------------------------------------------------------------------- +server_require_peer_cert_do_not_allow_partial_chain() -> + [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " + "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. + +server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ?config(server_verification_opts, Config)], + ClientOpts = ?config(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + unknown_ca + end, + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts)]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + receive + {Server, {error, {tls_alert, "unknown ca"}}} -> + receive + {Client, {error, {tls_alert, "unknown ca"}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. + + %%-------------------------------------------------------------------- +server_require_peer_cert_partial_chain_fun_fail() -> + [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. + +server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ?config(server_verification_opts, Config)], + ClientOpts = ?config(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + ture = false %% crash on purpose + end, + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts)]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + receive + {Server, {error, {tls_alert, "unknown ca"}}} -> + receive + {Client, {error, {tls_alert, "unknown ca"}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. + +%%-------------------------------------------------------------------- verify_fun_always_run_client() -> [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. @@ -434,10 +595,16 @@ cert_expired(Config) when is_list(Config) -> Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {options, [{verify, verify_peer} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, {error, {tls_alert, "certificate expired"}}, - Client, {error, {tls_alert, "certificate expired"}}). + {options, [{verify, verify_peer} | ClientOpts]}]), + receive + {Client, {error, {tls_alert, "certificate expired"}}} -> + receive + {Server, {error, {tls_alert, "certificate expired"}}} -> + ok; + {Server, {error, closed}} -> + ok + end + end. two_digits_str(N) when N < 10 -> lists:flatten(io_lib:format("0~p", [N])); @@ -632,7 +799,7 @@ no_authority_key_identifier() -> no_authority_key_identifier(Config) when is_list(Config) -> ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), PrivDir = ?config(priv_dir, Config), KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), @@ -804,7 +971,7 @@ unknown_server_ca_fail() -> [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. unknown_server_ca_fail(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -833,11 +1000,11 @@ unknown_server_ca_fail(Config) when is_list(Config) -> {verify_fun, FunAndState} | ClientOpts]}]), receive - {Server, {error, {tls_alert, "unknown ca"}}} -> + {Client, {error, {tls_alert, "unknown ca"}}} -> receive - {Client, {error, {tls_alert, "unknown ca"}}} -> + {Server, {error, {tls_alert, "unknown ca"}}} -> ok; - {Client, {error, closed}} -> + {Server, {error, closed}} -> ok end end. @@ -848,7 +1015,7 @@ unknown_server_ca_accept_verify_none() -> [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -873,7 +1040,7 @@ unknown_server_ca_accept_verify_peer() -> " with a verify_fun that accepts the unknown ca error"}]. unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -912,7 +1079,7 @@ unknown_server_ca_accept_backwardscompatibility() -> [{doc,"Test that old style verify_funs will work"}]. unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index e5e942ce1b..8dca733526 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -39,6 +39,7 @@ all() -> [decode_hello_handshake, decode_unknown_hello_extension_correctly, encode_single_hello_sni_extension_correctly, decode_single_hello_sni_extension_correctly, + decode_empty_server_sni_correctly, select_proper_tls_1_2_rsa_default_hashsign]. %%-------------------------------------------------------------------- @@ -106,6 +107,13 @@ decode_single_hello_sni_extension_correctly(_Config) -> Decoded = ssl_handshake:decode_hello_extensions(SNI), Exts = Decoded. +decode_empty_server_sni_correctly(_Config) -> + Exts = #hello_extensions{sni = ""}, + SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>, + Decoded = ssl_handshake:decode_hello_extensions(SNI), + Exts = Decoded. + + select_proper_tls_1_2_rsa_default_hashsign(_Config) -> % RFC 5246 section 7.4.1.4.1 tells to use {sha1,rsa} as default signature_algorithm for RSA key exchanges {sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,3}), diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 004cacf7fc..da20ed8593 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 5.3.5 +SSL_VSN = 5.3.7 diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index 5e74616099..ebc750a399 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -30,6 +30,79 @@ </header> <p>This document describes the changes made to the STDLIB application.</p> +<section><title>STDLIB 2.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The type spec of the FormFunc argument to + sys:handle_debug/4 was erroneously pointing to dbg_fun(). + This is now corrected and the new type is format_fun().</p> + <p> + Own Id: OTP-11800</p> + </item> + <item> + <p> + Behaviors such as gen_fsm and gen_server should always + invoke format_status/2 before printing the state to the + logs.</p> + <p> + Own Id: OTP-11967</p> + </item> + <item> + <p> The documentation of <c>dets:insert_new/2</c> has + been corrected. (Thanks to Alexei Sholik for reporting + the bug.) </p> + <p> + Own Id: OTP-12024</p> + </item> + <item> + <p> + Printing a term with io_lib:format and control sequence + w, precision P and field width F, where F< P would + fail in one of the two following ways:</p> + <p> + 1) If P < printed length of the term, an infinite loop + would be entered, consuming all available memory.</p> + <p> + 2) If P >= printed length of the term, an exception + would be raised.</p> + <p> + These two problems are now corrected.</p> + <p> + Own Id: OTP-12041</p> + </item> + <item> + <p> + The documentation of <c>maps:values/1</c> has been + corrected.</p> + <p> + Own Id: OTP-12055</p> + </item> + <item> + <p> + Expand shell functions in map expressions.</p> + <p> + Own Id: OTP-12063</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add maps:with/2</p> + <p> + Own Id: OTP-12137</p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 2.1.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml index c96cc95a44..b05d5cbc08 100644 --- a/lib/stdlib/doc/src/string.xml +++ b/lib/stdlib/doc/src/string.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2013</year> + <year>1996</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -124,6 +124,10 @@ <code type="none"> > tokens("abc defxxghix jkl", "x "). ["abc", "def", "ghi", "jkl"] </code> + <p>Note that, as shown in the example above, two or more + adjacent separator characters in <c><anno>String</anno></c> + will be treated as one. That is, there will not be any empty + strings in the resulting list of tokens.</p> </desc> </func> <func> diff --git a/lib/stdlib/src/dets.erl b/lib/stdlib/src/dets.erl index 76e03bbfaa..a4bd45ea19 100644 --- a/lib/stdlib/src/dets.erl +++ b/lib/stdlib/src/dets.erl @@ -2839,17 +2839,22 @@ fsck_try(Fd, Tab, FH, Fname, SlotNumbers, Version) -> tempfile(Fname) -> Tmp = lists:concat([Fname, ".TMP"]), - tempfile(Tmp, 10). - -tempfile(Tmp, 0) -> - Tmp; -tempfile(Tmp, N) -> case file:delete(Tmp) of - {error, eacces} -> % 'dets_process_died' happened anyway... (W-nd-ws) - timer:sleep(1000), - tempfile(Tmp, N-1); - _ -> - Tmp + {error, _Reason} -> % typically enoent + ok; + ok -> + assure_no_file(Tmp) + end, + Tmp. + +assure_no_file(File) -> + case file:read_file_info(File) of + {ok, _FileInfo} -> + %% Wait for some other process to close the file: + timer:sleep(100), + assure_no_file(File); + {error, _} -> + ok end. %% -> {ok, NewHead} | {try_again, integer()} | Error diff --git a/lib/stdlib/src/dets_server.erl b/lib/stdlib/src/dets_server.erl index 268c201047..3164d40f35 100644 --- a/lib/stdlib/src/dets_server.erl +++ b/lib/stdlib/src/dets_server.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2013. All Rights Reserved. +%% Copyright Ericsson AB 2001-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -171,9 +171,15 @@ handle_info({pending_reply, {Ref, Result0}}, State) -> link(Pid), do_link(Store, FromPid), true = ets:insert(Store, {FromPid, Tab}), - true = ets:insert(?REGISTRY, {Tab, 1, Pid}), - true = ets:insert(?OWNERS, {Pid, Tab}), + %% do_internal_open() has already done the following: + %% true = ets:insert(?REGISTRY, {Tab, 1, Pid}), + %% true = ets:insert(?OWNERS, {Pid, Tab}), {ok, Tab}; + {Reply, internal_open} -> + %% Clean up what do_internal_open() did: + true = ets:delete(?REGISTRY, Tab), + true = ets:delete(?OWNERS, Pid), + Reply; {Reply, _} -> % ok or Error Reply end, @@ -309,6 +315,12 @@ do_internal_open(State, From, Args) -> [T, _, _] -> T; [_, _] -> Ref end, + %% Pretend the table is open. If someone else tries to + %% open the file it will always become a pending + %% 'add_user' request. If someone tries to use the table + %% there will be a delay, but that is OK. + true = ets:insert(?REGISTRY, {Tab, 1, Pid}), + true = ets:insert(?OWNERS, {Pid, Tab}), pending_call(Tab, Pid, Ref, From, Args, internal_open, State); Error -> {Error, State} diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl index be9a4f5107..b3bc5f6d92 100644 --- a/lib/stdlib/src/edlin.erl +++ b/lib/stdlib/src/edlin.erl @@ -390,7 +390,7 @@ do_op(end_of_line, Bef, [C|Aft], Rs) -> do_op(end_of_line, Bef, [], Rs) -> {{Bef,[]},Rs}; do_op(ctlu, Bef, Aft, Rs) -> - put(kill_buffer, Bef), + put(kill_buffer, reverse(Bef)), {{[], Aft}, [{delete_chars, -length(Bef)} | Rs]}; do_op(beep, Bef, Aft, Rs) -> {{Bef,Aft},[beep|Rs]}; diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index e1ae3b7aea..1d4a2a1fef 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -753,6 +753,9 @@ attribute_farity({cons,L,H,T}) -> attribute_farity({tuple,L,Args0}) -> Args = attribute_farity_list(Args0), {tuple,L,Args}; +attribute_farity({map,L,Args0}) -> + Args = attribute_farity_map(Args0), + {map,L,Args}; attribute_farity({op,L,'/',{atom,_,_}=Name,{integer,_,_}=Arity}) -> {tuple,L,[Name,Arity]}; attribute_farity(Other) -> Other. @@ -760,6 +763,10 @@ attribute_farity(Other) -> Other. attribute_farity_list(Args) -> [attribute_farity(A) || A <- Args]. +%% It is not meaningful to have farity keys. +attribute_farity_map(Args) -> + [{Op,L,K,attribute_farity(V)} || {Op,L,K,V} <- Args]. + -spec error_bad_decl(integer(), attributes()) -> no_return(). error_bad_decl(L, S) -> @@ -954,7 +961,9 @@ abstract([H|T], L, none=E) -> abstract(List, L, E) when is_list(List) -> abstract_list(List, [], L, E); abstract(Tuple, L, E) when is_tuple(Tuple) -> - {tuple,L,abstract_tuple_list(tuple_to_list(Tuple), L, E)}. + {tuple,L,abstract_tuple_list(tuple_to_list(Tuple), L, E)}; +abstract(Map, L, E) when is_map(Map) -> + {map,L,abstract_map_fields(maps:to_list(Map),L,E)}. abstract_list([H|T], String, L, E) -> case is_integer(H) andalso H >= 0 andalso E(H) of @@ -979,6 +988,9 @@ abstract_tuple_list([H|T], L, E) -> abstract_tuple_list([], _L, _E) -> []. +abstract_map_fields(Fs,L,E) -> + [{map_field_assoc,L,abstract(K,L,E),abstract(V,L,E)}||{K,V}<-Fs]. + abstract_byte(Byte, L) when is_integer(Byte) -> {bin_element, L, {integer, L, Byte}, default, default}; abstract_byte(Bits, L) -> diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl index 82bc2c1460..1fd6d2a8df 100644 --- a/lib/stdlib/src/erl_pp.erl +++ b/lib/stdlib/src/erl_pp.erl @@ -300,7 +300,15 @@ map_pair_types(Fs) -> tuple_type(Fs, fun map_pair_type/1). map_pair_type({type,_Line,map_field_assoc,Ktype,Vtype}) -> - {seq,[],[]," =>",[ltype(Ktype),ltype(Vtype)]}. + map_assoc_typed(ltype(Ktype), Vtype). + +map_assoc_typed(B, {type,_,union,Ts}) -> + {first,[B,$\s],{seq,[],[],[],map_assoc_union_type(Ts)}}; +map_assoc_typed(B, Type) -> + {list,[{cstep,[B," =>"],ltype(Type)}]}. + +map_assoc_union_type([T|Ts]) -> + [[leaf("=> "),ltype(T)] | ltypes(Ts, fun union_elem/1)]. record_type(Name, Fields) -> {first,[record_name(Name)],field_types(Fields)}. diff --git a/lib/stdlib/src/filelib.erl b/lib/stdlib/src/filelib.erl index 9efbe8da20..daae1fd2d2 100644 --- a/lib/stdlib/src/filelib.erl +++ b/lib/stdlib/src/filelib.erl @@ -371,7 +371,7 @@ compile_wildcard(Pattern, Cwd0) -> [Root|Rest] = filename:split(Pattern), case filename:pathtype(Root) of relative -> - Cwd = filename:join([Cwd0]), + Cwd = prepare_base(Cwd0), compile_wildcard_2([Root|Rest], {cwd,Cwd}); _ -> compile_wildcard_2(Rest, {root,0,Root}) diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index d39dd89d3a..469acdc37c 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -49,8 +49,6 @@ -import(error_logger, [error_msg/2]). --define(reply(X), From ! {element(2,Tag), X}). - -record(handler, {module :: atom(), id = false, state, @@ -249,49 +247,49 @@ handle_msg(Msg, Parent, ServerName, MSL, Debug) -> {notify, Event} -> {Hib,MSL1} = server_notify(Event, handle_event, MSL, ServerName), loop(Parent, ServerName, MSL1, Debug, Hib); - {From, Tag, {sync_notify, Event}} -> + {_From, Tag, {sync_notify, Event}} -> {Hib, MSL1} = server_notify(Event, handle_event, MSL, ServerName), - ?reply(ok), + reply(Tag, ok), loop(Parent, ServerName, MSL1, Debug, Hib); {'EXIT', From, Reason} -> MSL1 = handle_exit(From, Reason, MSL, ServerName), loop(Parent, ServerName, MSL1, Debug, false); - {From, Tag, {call, Handler, Query}} -> + {_From, Tag, {call, Handler, Query}} -> {Hib, Reply, MSL1} = server_call(Handler, Query, MSL, ServerName), - ?reply(Reply), + reply(Tag, Reply), loop(Parent, ServerName, MSL1, Debug, Hib); - {From, Tag, {add_handler, Handler, Args}} -> + {_From, Tag, {add_handler, Handler, Args}} -> {Hib, Reply, MSL1} = server_add_handler(Handler, Args, MSL), - ?reply(Reply), + reply(Tag, Reply), loop(Parent, ServerName, MSL1, Debug, Hib); - {From, Tag, {add_sup_handler, Handler, Args, SupP}} -> + {_From, Tag, {add_sup_handler, Handler, Args, SupP}} -> {Hib, Reply, MSL1} = server_add_sup_handler(Handler, Args, MSL, SupP), - ?reply(Reply), + reply(Tag, Reply), loop(Parent, ServerName, MSL1, Debug, Hib); - {From, Tag, {delete_handler, Handler, Args}} -> + {_From, Tag, {delete_handler, Handler, Args}} -> {Reply, MSL1} = server_delete_handler(Handler, Args, MSL, ServerName), - ?reply(Reply), + reply(Tag, Reply), loop(Parent, ServerName, MSL1, Debug, false); - {From, Tag, {swap_handler, Handler1, Args1, Handler2, Args2}} -> + {_From, Tag, {swap_handler, Handler1, Args1, Handler2, Args2}} -> {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, ServerName), - ?reply(Reply), + reply(Tag, Reply), loop(Parent, ServerName, MSL1, Debug, Hib); - {From, Tag, {swap_sup_handler, Handler1, Args1, Handler2, Args2, + {_From, Tag, {swap_sup_handler, Handler1, Args1, Handler2, Args2, Sup}} -> {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, Sup, ServerName), - ?reply(Reply), + reply(Tag, Reply), loop(Parent, ServerName, MSL1, Debug, Hib); - {From, Tag, stop} -> + {_From, Tag, stop} -> catch terminate_server(normal, Parent, MSL, ServerName), - ?reply(ok); - {From, Tag, which_handlers} -> - ?reply(the_handlers(MSL)), + reply(Tag, ok); + {_From, Tag, which_handlers} -> + reply(Tag, the_handlers(MSL)), loop(Parent, ServerName, MSL, Debug, false); - {From, Tag, get_modules} -> - ?reply(get_modules(MSL)), + {_From, Tag, get_modules} -> + reply(Tag, get_modules(MSL)), loop(Parent, ServerName, MSL, Debug, false); Other -> {Hib, MSL1} = server_notify(Other, handle_info, MSL, ServerName), @@ -303,6 +301,10 @@ terminate_server(Reason, Parent, MSL, ServerName) -> do_unlink(Parent, MSL), exit(Reason). +reply({From, Ref}, Msg) -> + From ! {Ref, Msg}, + ok. + %% unlink the supervisor process of all supervised handlers. %% We do not want a handler supervisor to EXIT due to the %% termination of the event manager (server). diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index c0ee8799c8..6c25beabe9 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -421,13 +421,13 @@ obsolete_1(ssh_cm, stop_listener, 1) -> obsolete_1(ssh_cm, session_open, A) when A =:= 2; A =:= 4 -> {removed,{ssh_connection,session_channel,A},"R14B"}; obsolete_1(ssh_cm, direct_tcpip, A) when A =:= 6; A =:= 8 -> - {removed,{ssh_connection,direct_tcpip,A}}; + {removed,{ssh_connection,direct_tcpip,A},"R14B"}; obsolete_1(ssh_cm, tcpip_forward, 3) -> {removed,{ssh_connection,tcpip_forward,3},"R14B"}; obsolete_1(ssh_cm, cancel_tcpip_forward, 3) -> {removed,{ssh_connection,cancel_tcpip_forward,3},"R14B"}; obsolete_1(ssh_cm, open_pty, A) when A =:= 3; A =:= 7; A =:= 9 -> - {removed,{ssh_connection,open_pty,A},"R14"}; + {removed,{ssh_connection,open_pty,A},"R14B"}; obsolete_1(ssh_cm, setenv, 5) -> {removed,{ssh_connection,setenv,5},"R14B"}; obsolete_1(ssh_cm, shell, 2) -> @@ -441,11 +441,11 @@ obsolete_1(ssh_cm, winch, A) when A =:= 4; A =:= 6 -> obsolete_1(ssh_cm, signal, 3) -> {removed,{ssh_connection,signal,3},"R14B"}; obsolete_1(ssh_cm, attach, A) when A =:= 2; A =:= 3 -> - {removed,{ssh,attach,A}}; + {removed,"no longer useful; removed in R14B"}; obsolete_1(ssh_cm, detach, 2) -> - {removed,"no longer useful; will be removed in R14B"}; + {removed,"no longer useful; removed in R14B"}; obsolete_1(ssh_cm, set_user_ack, 4) -> - {removed,"no longer useful; will be removed in R14B"}; + {removed,"no longer useful; removed in R14B"}; obsolete_1(ssh_cm, adjust_window, 3) -> {removed,{ssh_connection,adjust_window,3},"R14B"}; obsolete_1(ssh_cm, close, 2) -> @@ -461,9 +461,9 @@ obsolete_1(ssh_cm, send_ack, A) when 3 =< A, A =< 5 -> obsolete_1(ssh_ssh, connect, A) when 1 =< A, A =< 3 -> {removed,{ssh,shell,A},"R14B"}; obsolete_1(ssh_sshd, listen, A) when 0 =< A, A =< 3 -> - {removed,{ssh,daemon,[1,2,3]},"R14"}; + {removed,{ssh,daemon,[1,2,3]},"R14B"}; obsolete_1(ssh_sshd, stop, 1) -> - {removed,{ssh,stop_listener,1}}; + {removed,{ssh,stop_listener,1},"R14B"}; %% Added in R13A. obsolete_1(regexp, _, _) -> diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src index 99d9b8b431..7802ea884f 100644 --- a/lib/stdlib/src/stdlib.appup.src +++ b/lib/stdlib/src/stdlib.appup.src @@ -17,11 +17,11 @@ %% %CopyrightEnd% {"%VSN%", %% Up from - max one major revision back - [{<<"2\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, %% 17.1 + [{<<"2\\.[1-2](\\.[0-9]+)*">>,[restart_new_emulator]}, %% 17.1-17.3 {<<"2\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, %% 17.0 {<<"1\\.19(\\.[0-9]+)*">>,[restart_new_emulator]}],%% R16 %% Down to - max one major revision back - [{<<"2\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, %% 17.1 + [{<<"2\\.[1-2](\\.[0-9]+)*">>,[restart_new_emulator]}, %% 17.1-17.3 {<<"2\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, %% 17.0 {<<"1\\.19(\\.[0-9]+)*">>,[restart_new_emulator]}] %% R16 }. diff --git a/lib/stdlib/test/dets_SUITE.erl b/lib/stdlib/test/dets_SUITE.erl index 119b4dc7cb..3b08ac165e 100644 --- a/lib/stdlib/test/dets_SUITE.erl +++ b/lib/stdlib/test/dets_SUITE.erl @@ -223,8 +223,7 @@ open(Config, Version) -> ?format("Crashing dets server \n", []), process_flag(trap_exit, true), - Procs = [whereis(?DETS_SERVER) | map(fun(Tab) -> dets:info(Tab, pid) end, - Tabs)], + Procs = [whereis(?DETS_SERVER) | [dets:info(Tab, pid) || Tab <- Tabs]], foreach(fun(Pid) -> exit(Pid, kill) end, Procs), timer:sleep(100), c:flush(), %% flush all the EXIT sigs @@ -235,18 +234,32 @@ open(Config, Version) -> open_files(1, All, Version), ?format("Checking contents of repaired files \n", []), check(Tabs, Data), - - close_all(Tabs), + close_all(Tabs), delete_files(All), - P1 = pps(), + {Ports0, Procs0} = P0, - {Ports1, Procs1} = P1, - true = Ports1 =:= Ports0, - %% The dets_server process has been restarted: - [_] = Procs0 -- Procs1, - [_] = Procs1 -- Procs0, - ok. + Test = fun() -> + P1 = pps(), + {Ports1, Procs1} = P1, + show("Old port", Ports0 -- Ports1), + show("New port", Ports1 -- Ports0), + show("Old procs", Procs0 -- Procs1), + show("New procs", Procs1 -- Procs0), + io:format("Remaining Dets-pids (should be nil): ~p~n", + [find_dets_pids()]), + true = Ports1 =:= Ports0, + %% The dets_server process has been restarted: + [_] = Procs0 -- Procs1, + [_] = Procs1 -- Procs0, + ok + end, + case catch Test() of + ok -> ok; + _ -> + timer:sleep(500), + ok = Test() + end. check(Tabs, Data) -> foreach(fun(Tab) -> @@ -3275,12 +3288,22 @@ simultaneous_open(Config) -> File = filename(Tab, Config), ok = monit(Tab, File), - ok = kill_while_repairing(Tab, File), - ok = kill_while_init(Tab, File), - ok = open_ro(Tab, File), - ok = open_w(Tab, File, 0, Config), - ok = open_w(Tab, File, 100, Config), - ok. + case feasible() of + false -> {comment, "OK, but did not run all of the test"}; + true -> + ok = kill_while_repairing(Tab, File), + ok = kill_while_init(Tab, File), + ok = open_ro(Tab, File), + ok = open_w(Tab, File, 0, Config), + ok = open_w(Tab, File, 100, Config) + end. + +feasible() -> + LP = erlang:system_info(logical_processors), + (is_integer(LP) + andalso LP >= erlang:system_info(schedulers_online) + andalso not erlang:system_info(debug_compiled) + andalso not erlang:system_info(lock_checking)). %% One process logs and another process closes the log. Before %% monitors were used, this would make the client never return. @@ -3307,7 +3330,6 @@ kill_while_repairing(Tab, File) -> Delay = 1000, dets:start(), Parent = self(), - Ps = processes(), F = fun() -> R = (catch dets:open_file(Tab, [{file,File}])), timer:sleep(Delay), @@ -3318,7 +3340,7 @@ kill_while_repairing(Tab, File) -> P1 = spawn(F), P2 = spawn(F), P3 = spawn(F), - DetsPid = find_dets_pid([P1, P2, P3 | Ps]), + DetsPid = find_dets_pid(), exit(DetsPid, kill), receive {P1,R1} -> R1 end, @@ -3342,12 +3364,6 @@ kill_while_repairing(Tab, File) -> file:delete(File), ok. -find_dets_pid(P0) -> - case lists:sort(processes() -- P0) of - [P, _] -> P; - _ -> timer:sleep(100), find_dets_pid(P0) - end. - find_dets_pid() -> case find_dets_pids() of [] -> @@ -3421,6 +3437,13 @@ open_ro(Tab, File) -> open_w(Tab, File, Delay, Config) -> create_opened_log(File), + + Tab2 = t2, + File2 = filename(Tab2, Config), + file:delete(File2), + {ok,Tab2} = dets:open_file(Tab2, [{file,File2}]), + ok = dets:close(Tab2), + Parent = self(), F = fun() -> R = dets:open_file(Tab, [{file,File}]), @@ -3430,16 +3453,16 @@ open_w(Tab, File, Delay, Config) -> Pid1 = spawn(F), Pid2 = spawn(F), Pid3 = spawn(F), - undefined = dets:info(Tab), % is repairing now - 0 = qlen(), - Tab2 = t2, - File2 = filename(Tab2, Config), - file:delete(File2), + ok = wait_for_repair_to_start(Tab), + + %% It is assumed that it takes some time to repair the file. {ok,Tab2} = dets:open_file(Tab2, [{file,File2}]), + %% The Dets server managed to handle to open_file request. + 0 = qlen(), % still repairing + ok = dets:close(Tab2), file:delete(File2), - 0 = qlen(), % still repairing receive {Pid1,R1} -> {ok, Tab} = R1 end, receive {Pid2,R2} -> {ok, Tab} = R2 end, @@ -3456,6 +3479,15 @@ open_w(Tab, File, Delay, Config) -> file:delete(File), ok. +wait_for_repair_to_start(Tab) -> + case catch dets_server:get_pid(Tab) of + {'EXIT', _} -> + timer:sleep(1), + wait_for_repair_to_start(Tab); + Pid when is_pid(Pid) -> + ok + end. + qlen() -> {_, {_, N}} = lists:keysearch(message_queue_len, 1, process_info(self())), N. @@ -4350,6 +4382,7 @@ check_badarg({'EXIT', {badarg, [{M,F,A,_} | _]}}, M, F, Args) -> true = test_server:is_native(M) andalso length(Args) =:= A. check_pps({Ports0,Procs0} = P0) -> + ok = check_dets_tables(), case pps() of P0 -> ok; @@ -4375,13 +4408,45 @@ check_pps({Ports0,Procs0} = P0) -> end end. +%% Copied from dets_server.erl: +-define(REGISTRY, dets_registry). +-define(OWNERS, dets_owners). +-define(STORE, dets). + +check_dets_tables() -> + Store = [T || + T <- ets:all(), + ets:info(T, name) =:= ?STORE, + owner(T) =:= dets], + S = case Store of + [Tab] -> ets:tab2list(Tab); + [] -> [] + end, + case {ets:tab2list(?REGISTRY), ets:tab2list(?OWNERS), S} of + {[], [], []} -> ok; + {R, O, _} -> + io:format("Registry: ~p~n", [R]), + io:format("Owners: ~p~n", [O]), + io:format("Store: ~p~n", [S]), + not_ok + end. + +owner(Tab) -> + Owner = ets:info(Tab, owner), + case process_info(Owner, registered_name) of + {registered_name, Name} -> Name; + _ -> Owner + end. + show(_S, []) -> ok; -show(S, [Pid|Pids]) when is_pid(Pid) -> - io:format("~s: ~p~n", [S, erlang:process_info(Pid)]), +show(S, [{Pid, Name, InitCall}|Pids]) when is_pid(Pid) -> + io:format("~s: ~w (~w), ~w: ~p~n", + [S, Pid, proc_reg_name(Name), InitCall, + erlang:process_info(Pid)]), show(S, Pids); -show(S, [Port|Ports]) when is_port(Port)-> - io:format("~s: ~p~n", [S, erlang:port_info(Port)]), +show(S, [{Port, _}|Ports]) when is_port(Port)-> + io:format("~s: ~w: ~p~n", [S, Port, erlang:port_info(Port)]), show(S, Ports). pps() -> @@ -4397,5 +4462,8 @@ process_list() -> safe_second_element(process_info(P, initial_call))} || P <- processes()]. +proc_reg_name({registered_name, Name}) -> Name; +proc_reg_name([]) -> no_reg_name. + safe_second_element({_,Info}) -> Info; safe_second_element(Other) -> Other. diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl index babf3a49eb..927fe0b595 100644 --- a/lib/stdlib/test/erl_pp_SUITE.erl +++ b/lib/stdlib/test/erl_pp_SUITE.erl @@ -604,20 +604,20 @@ import_export(Config) when is_list(Config) -> misc_attrs(suite) -> []; misc_attrs(Config) when is_list(Config) -> - ?line ok = pp_forms(<<"-module(m). ">>), - ?line ok = pp_forms(<<"-module(m, [Aafjlksfjdlsjflsdfjlsdjflkdsfjlk," - "Blsjfdlslfjsdf]). ">>), - ?line ok = pp_forms(<<"-export([]). ">>), - ?line ok = pp_forms(<<"-export([foo/2, bar/0]). ">>), - ?line ok = pp_forms(<<"-export([bar/0]). ">>), - ?line ok = pp_forms(<<"-import(lists, []). ">>), - ?line ok = pp_forms(<<"-import(lists, [map/2]). ">>), - ?line ok = pp_forms(<<"-import(lists, [map/2, foreach/2]). ">>), - ?line ok = pp_forms(<<"-'wild '({attr2,3}). ">>), - ?line ok = pp_forms(<<"-record(a, {b,c}). ">>), - ?line ok = pp_forms(<<"-record(' a ', {}). ">>), - ?line ok = pp_forms(<<"-record(' a ', {foo = foo:bar()}). ">>), - + ok = pp_forms(<<"-module(m). ">>), + ok = pp_forms(<<"-module(m, [Aafjlksfjdlsjflsdfjlsdjflkdsfjlk," + "Blsjfdlslfjsdf]). ">>), + ok = pp_forms(<<"-export([]). ">>), + ok = pp_forms(<<"-export([foo/2, bar/0]). ">>), + ok = pp_forms(<<"-export([bar/0]). ">>), + ok = pp_forms(<<"-import(lists, []). ">>), + ok = pp_forms(<<"-import(lists, [map/2]). ">>), + ok = pp_forms(<<"-import(lists, [map/2, foreach/2]). ">>), + ok = pp_forms(<<"-'wild '({attr2,3}). ">>), + ok = pp_forms(<<"-record(a, {b,c}). ">>), + ok = pp_forms(<<"-record(' a ', {}). ">>), + ok = pp_forms(<<"-record(' a ', {foo = foo:bar()}). ">>), + ok = pp_forms(<<"-custom1(#{test1 => init/2, test2 => [val/1, val/2]}). ">>), ok. dialyzer_attrs(suite) -> diff --git a/lib/stdlib/test/filelib_SUITE.erl b/lib/stdlib/test/filelib_SUITE.erl index 040ae1effc..bd313390b3 100644 --- a/lib/stdlib/test/filelib_SUITE.erl +++ b/lib/stdlib/test/filelib_SUITE.erl @@ -88,6 +88,7 @@ wildcard_two(Config) when is_list(Config) -> ?line ok = file:make_dir(Dir), ?line do_wildcard_1(Dir, fun(Wc) -> io:format("~p~n",[{Wc,Dir, X = filelib:wildcard(Wc, Dir)}]),X end), ?line do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, Dir++"/") end), + ?line do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, Dir++"/.") end), case os:type() of {win32,_} -> ok; diff --git a/lib/stdlib/test/stdlib_SUITE.erl b/lib/stdlib/test/stdlib_SUITE.erl index 59821220b4..3d09bd27ff 100644 --- a/lib/stdlib/test/stdlib_SUITE.erl +++ b/lib/stdlib/test/stdlib_SUITE.erl @@ -78,17 +78,29 @@ appup_test(_Config) -> appup_tests(_App,{[],[]}) -> {skip,"no previous releases available"}; -appup_tests(App,{OkVsns,NokVsns}) -> +appup_tests(App,{OkVsns0,NokVsns}) -> application:load(App), {_,_,Vsn} = lists:keyfind(App,1,application:loaded_applications()), AppupFileName = atom_to_list(App) ++ ".appup", AppupFile = filename:join([code:lib_dir(App),ebin,AppupFileName]), {ok,[{Vsn,UpFrom,DownTo}=AppupScript]} = file:consult(AppupFile), ct:log("~p~n",[AppupScript]), - ct:log("Testing ok versions: ~p~n",[OkVsns]), + OkVsns = + case OkVsns0 -- [Vsn] of + OkVsns0 -> + OkVsns0; + Ok -> + ct:log("Current version, ~p, is same as in previous release.~n" + "Removing this from the list of ok versions.", + [Vsn]), + Ok + end, + ct:log("Testing that appup allows upgrade from these versions: ~p~n", + [OkVsns]), check_appup(OkVsns,UpFrom,{ok,[restart_new_emulator]}), check_appup(OkVsns,DownTo,{ok,[restart_new_emulator]}), - ct:log("Testing not ok versions: ~p~n",[NokVsns]), + ct:log("Testing that appup does not allow upgrade from these versions: ~p~n", + [NokVsns]), check_appup(NokVsns,UpFrom,error), check_appup(NokVsns,DownTo,error), ok. diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index 9881ab040e..b522c3ea3c 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 2.1.1 +STDLIB_VSN = 2.2 diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 46a5ca48df..40372a2106 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -669,6 +669,9 @@ is_leaf(Node) -> operator -> true; % nonstandard type string -> true; text -> true; % nonstandard type + map_expr -> + map_expr_fields(Node) =:= [] andalso + map_expr_argument(Node) =:= none; tuple -> tuple_elements(Node) =:= []; underscore -> true; variable -> true; @@ -6093,6 +6096,9 @@ abstract([]) -> nil(); abstract(T) when is_tuple(T) -> tuple(abstract_list(tuple_to_list(T))); +abstract(T) when is_map(T) -> + map_expr([map_field_assoc(abstract(Key),abstract(Value)) + || {Key,Value} <- maps:to_list(T)]); abstract(T) when is_binary(T) -> binary([binary_field(integer(B)) || B <- binary_to_list(T)]); abstract(T) -> @@ -6154,6 +6160,14 @@ concrete(Node) -> | concrete(list_tail(Node))]; tuple -> list_to_tuple(concrete_list(tuple_elements(Node))); + map_expr -> + As = [tuple([map_field_assoc_name(F), + map_field_assoc_value(F)]) || F <- map_expr_fields(Node)], + M0 = maps:from_list(concrete_list(As)), + case map_expr_argument(Node) of + none -> M0; + Node0 -> maps:merge(concrete(Node0),M0) + end; binary -> Fs = [revert_binary_field( binary_field(binary_field_body(F), @@ -6209,10 +6223,31 @@ is_literal(T) -> is_literal(list_head(T)) andalso is_literal(list_tail(T)); tuple -> lists:all(fun is_literal/1, tuple_elements(T)); + map_expr -> + case map_expr_argument(T) of + none -> true; + Arg -> is_literal(Arg) + end andalso lists:all(fun is_literal_map_field/1, map_expr_fields(T)); + binary -> + lists:all(fun is_literal_binary_field/1, binary_fields(T)); _ -> false end. +is_literal_binary_field(F) -> + case binary_field_types(F) of + [] -> is_literal(binary_field_body(F)); + _ -> false + end. + +is_literal_map_field(F) -> + case type(F) of + map_field_assoc -> + is_literal(map_field_assoc_name(F)) andalso + is_literal(map_field_assoc_value(F)); + map_field_exact -> + false + end. %% ===================================================================== %% @doc Returns an `erl_parse'-compatible representation of a diff --git a/lib/syntax_tools/test/Makefile b/lib/syntax_tools/test/Makefile index d4733b9a42..f67e3f8984 100644 --- a/lib/syntax_tools/test/Makefile +++ b/lib/syntax_tools/test/Makefile @@ -61,5 +61,6 @@ release_tests_spec: make_emakefile $(INSTALL_DATA) $(EMAKEFILE) $(ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) syntax_tools.spec syntax_tools.cover "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" + @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) release_docs_spec: diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl index 6fb3e5ccfb..3c6b33f459 100644 --- a/lib/syntax_tools/test/syntax_tools_SUITE.erl +++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl @@ -24,12 +24,16 @@ init_per_group/2,end_per_group/2]). %% Test cases --export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1]). +-export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1, + t_abstract_type/1,t_erl_parse_type/1,t_epp_dodger/1, + t_comment_scan/1,t_igor/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [app_test,appup_test,smoke_test,revert,revert_map]. + [app_test,appup_test,smoke_test,revert,revert_map, + t_abstract_type,t_erl_parse_type,t_epp_dodger, + t_comment_scan,t_igor]. groups() -> []. @@ -54,15 +58,15 @@ appup_test(Config) when is_list(Config) -> %% Read and parse all source in the OTP release. smoke_test(Config) when is_list(Config) -> - ?line Dog = ?t:timetrap(?t:minutes(12)), - ?line Wc = filename:join([code:lib_dir(),"*","src","*.erl"]), - ?line Fs = filelib:wildcard(Wc), - ?line io:format("~p files\n", [length(Fs)]), - ?line case p_run(fun smoke_test_file/1, Fs) of - 0 -> ok; - N -> ?line ?t:fail({N,errors}) - end, - ?line ?t:timetrap_cancel(Dog). + Dog = ?t:timetrap(?t:minutes(12)), + Wc = filename:join([code:lib_dir(),"*","src","*.erl"]), + Fs = filelib:wildcard(Wc), + io:format("~p files\n", [length(Fs)]), + case p_run(fun smoke_test_file/1, Fs) of + 0 -> ok; + N -> ?t:fail({N,errors}) + end, + ?t:timetrap_cancel(Dog). smoke_test_file(File) -> case epp_dodger:parse_file(File) of @@ -94,9 +98,9 @@ revert(Config) when is_list(Config) -> io:format("~p files\n", [length(Fs)]), case p_run(fun (File) -> revert_file(File, Path) end, Fs) of 0 -> ok; - N -> ?line ?t:fail({N,errors}) + N -> ?t:fail({N,errors}) end, - ?line ?t:timetrap_cancel(Dog). + ?t:timetrap_cancel(Dog). revert_file(File, Path) -> case epp:parse_file(File, Path, []) of @@ -110,14 +114,298 @@ revert_file(File, Path) -> end. %% Testing bug fix for reverting map_field_assoc -revert_map(Config) -> +revert_map(Config) when is_list(Config) -> Dog = ?t:timetrap(?t:minutes(1)), - ?line [{map_field_assoc,16,{atom,17,name},{var,18,'Value'}}] = - erl_syntax:revert_forms([{tree,map_field_assoc, - {attr,16,[],none}, - {map_field_assoc, - {atom,17,name},{var,18,'Value'}}}]), - ?line ?t:timetrap_cancel(Dog). + [{map_field_assoc,16,{atom,17,name},{var,18,'Value'}}] = + erl_syntax:revert_forms([{tree,map_field_assoc, + {attr,16,[],none}, + {map_field_assoc,{atom,17,name},{var,18,'Value'}}}]), + ?t:timetrap_cancel(Dog). + + + +%% api tests + +t_abstract_type(Config) when is_list(Config) -> + F = fun validate_abstract_type/1, + ok = validate(F,[{hi,atom}, + {1,integer}, + {1.0,float}, + {$a,integer}, + {[],nil}, + {[<<1,2>>,a,b],list}, + {[2,3,<<1,2>>,a,b],list}, + {[$a,$b,$c],string}, + {"hello world",string}, + {<<1,2,3>>,binary}, + {#{a=>1,"b"=>2},map_expr}, + {#{#{i=>1}=>1,"b"=>#{v=>2}},map_expr}, + {{a,b,c},tuple}]), + ok. + +t_erl_parse_type(Config) when is_list(Config) -> + F = fun validate_erl_parse_type/1, + %% leaf types + ok = validate(F,[{"1",integer,true}, + {"123456789",integer,true}, + {"$h", char,true}, + {"3.1415", float,true}, + {"1.33e36", float,true}, + {"\"1.33e36: hello\"", string,true}, + {"Var1", variable,true}, + {"_", underscore,true}, + {"[]", nil,true}, + {"{}", tuple,true}, + {"#{}",map_expr,true}, + {"'some atom'", atom, true}]), + %% composite types + ok = validate(F,[{"case X of t -> t; f -> f end", case_expr,false}, + {"try X of t -> t catch C:R -> error end", try_expr,false}, + {"receive X -> X end", receive_expr,false}, + {"receive M -> X1 after T -> X2 end", receive_expr,false}, + {"catch (X)", catch_expr,false}, + {"fun(X) -> X end", fun_expr,false}, + {"fun Foo(X) -> X end", named_fun_expr,false}, + {"fun foo/2", implicit_fun,false}, + {"fun bar:foo/2", implicit_fun,false}, + {"if X -> t; true -> f end", if_expr,false}, + {"<<1,2,3,4>>", binary,false}, + {"<<1,2,3,4:5>>", binary,false}, + {"<<V1:63,V2:22/binary, V3/bits>>", binary,false}, + {"begin X end", block_expr,false}, + {"foo(X1,X2)", application,false}, + {"bar:foo(X1,X2)", application,false}, + {"[1,2,3,4]", list,false}, + {"[1|4]", list, false}, + {"[<<1>>,<<2>>,-2,<<>>,[more,list]]", list,false}, + {"[1|[2|[3|[4|[]]]]]", list,false}, + {"#{ a=>1, b=>2 }", map_expr,false}, + {"#{3=>3}#{ a=>1, b=>2 }", map_expr,false}, + {"#{ a:=1, b:=2 }", map_expr,false}, + {"M#{ a=>1, b=>2 }", map_expr,false}, + {"[V||V <- Vs]", list_comp,false}, + {"<< <<B>> || <<B>> <= Bs>>", binary_comp,false}, + {"#state{ a = A, b = B}", record_expr,false}, + {"#state{}", record_expr,false}, + {"#s{ a = #def{ a=A }, b = B}", record_expr,false}, + {"State#state{ a = A, b = B}", record_expr,false}, + {"State#state.a", record_access,false}, + {"#state.a", record_index_expr,false}, + {"-X", prefix_expr,false}, + {"X1 + X2", infix_expr,false}, + {"(X1 + X2) * X3", infix_expr,false}, + {"X1 = X2", match_expr,false}, + {"{a,b,c}", tuple,false}]), + ok. + +%% the macro ?MODULE seems faulty +t_epp_dodger(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + Filenames = ["syntax_tools_SUITE_test_module.erl", + "syntax_tools_test.erl"], + ok = test_epp_dodger(Filenames,DataDir,PrivDir), + ok. + +t_comment_scan(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Filenames = ["syntax_tools_SUITE_test_module.erl", + "syntax_tools_test.erl"], + ok = test_comment_scan(Filenames,DataDir), + ok. + +t_igor(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + FileM1 = filename:join(DataDir,"m1.erl"), + FileM2 = filename:join(DataDir,"m2.erl"), + ["m.erl",_]=R = igor:merge(m,[FileM1,FileM2],[{outdir,PrivDir}]), + io:format("igor:merge/3 = ~p~n", [R]), + ok. + +test_comment_scan([],_) -> ok; +test_comment_scan([File|Files],DataDir) -> + Filename = filename:join(DataDir,File), + {ok, Fs0} = epp:parse_file(Filename, [], []), + Comments = erl_comment_scan:file(Filename), + Fun = fun(Node) -> + case erl_syntax:is_form(Node) of + true -> + C1 = erl_syntax:comment(2,[" This is a form."]), + Node1 = erl_syntax:add_precomments([C1],Node), + Node1; + false -> + Node + end + end, + Fs1 = erl_recomment:recomment_forms(Fs0, Comments), + Fs2 = erl_syntax_lib:map(Fun, Fs1), + io:format("File: ~s~n", [Filename]), + io:put_chars(erl_prettypr:format(Fs2, [{paper, 120}, + {ribbon, 110}])), + test_comment_scan(Files,DataDir). + + +test_epp_dodger([], _, _) -> ok; +test_epp_dodger([Filename|Files],DataDir,PrivDir) -> + io:format("Parsing ~p~n", [Filename]), + InFile = filename:join(DataDir, Filename), + Parsers = [{fun epp_dodger:parse_file/1,parse_file}, + {fun epp_dodger:quick_parse_file/1,quick_parse_file}, + {fun (File) -> + {ok,Dev} = file:open(File,[read]), + Res = epp_dodger:parse(Dev), + file:close(File), + Res + end, parse}, + {fun (File) -> + {ok,Dev} = file:open(File,[read]), + Res = epp_dodger:quick_parse(Dev), + file:close(File), + Res + end, quick_parse}], + FsForms = parse_with(Parsers, InFile), + ok = pretty_print_parse_forms(FsForms,PrivDir,Filename), + test_epp_dodger(Files,DataDir,PrivDir). + +parse_with([],_) -> []; +parse_with([{Fun,ParserType}|Funs],File) -> + {ok, Fs} = Fun(File), + [{Fs,ParserType}|parse_with(Funs,File)]. + +pretty_print_parse_forms([],_,_) -> ok; +pretty_print_parse_forms([{Fs0,Type}|FsForms],PrivDir,Filename) -> + Parser = atom_to_list(Type), + OutFile = filename:join(PrivDir, Parser ++"_" ++ Filename), + io:format("Pretty print ~p (~w) to ~p~n", [Filename,Type,OutFile]), + Comment = fun (Node,{CntCase,CntTry}=Cnt) -> + case erl_syntax:type(Node) of + case_expr -> + C1 = erl_syntax:comment(2,["Before a case expression"]), + Node1 = erl_syntax:add_precomments([C1],Node), + C2 = erl_syntax:comment(2,["After a case expression"]), + Node2 = erl_syntax:add_postcomments([C2],Node1), + {Node2,{CntCase+1,CntTry}}; + try_expr -> + C1 = erl_syntax:comment(2,["Before a try expression"]), + Node1 = erl_syntax:set_precomments(Node, + erl_syntax:get_precomments(Node) ++ [C1]), + C2 = erl_syntax:comment(2,["After a try expression"]), + Node2 = erl_syntax:set_postcomments(Node1, + erl_syntax:get_postcomments(Node1) ++ [C2]), + {Node2,{CntCase,CntTry+1}}; + _ -> + {Node,Cnt} + end + end, + Fs1 = erl_syntax:form_list(Fs0), + {Fs2,{CC,CT}} = erl_syntax_lib:mapfold(Comment,{0,0}, Fs1), + io:format("Commented on ~w cases and ~w tries~n", [CC,CT]), + PP = erl_prettypr:format(Fs2), + ok = file:write_file(OutFile,iolist_to_binary(PP)), + pretty_print_parse_forms(FsForms,PrivDir,Filename). + + +validate(_,[]) -> ok; +validate(F,[V|Vs]) -> + ok = F(V), + validate(F,Vs). + + +validate_abstract_type({Lit,Type}) -> + Tree = erl_syntax:abstract(Lit), + ok = validate_special_type(Type,Tree), + Type = erl_syntax:type(Tree), + true = erl_syntax:is_literal(Tree), + ErlT = erl_syntax:revert(Tree), + Type = erl_syntax:type(ErlT), + ok = validate_special_type(Type,ErlT), + Conc = erl_syntax:concrete(Tree), + Lit = Conc, + ok. + +validate_erl_parse_type({String,Type,Leaf}) -> + ErlT = string_to_expr(String), + ok = validate_special_type(Type,ErlT), + Type = erl_syntax:type(ErlT), + Leaf = erl_syntax:is_leaf(ErlT), + Tree = erl_syntax_lib:map(fun(Node) -> Node end, ErlT), + Type = erl_syntax:type(Tree), + _ = erl_syntax:meta(Tree), + ok = validate_special_type(Type,Tree), + RevT = erl_syntax:revert(Tree), + ok = validate_special_type(Type,RevT), + Type = erl_syntax:type(RevT), + ok. + +validate_special_type(string,Node) -> + Val = erl_syntax:string_value(Node), + true = erl_syntax:is_string(Node,Val), + _ = erl_syntax:string_literal(Node), + ok; +validate_special_type(variable,Node) -> + _ = erl_syntax:variable_literal(Node), + ok; +validate_special_type(fun_expr,Node) -> + A = erl_syntax:fun_expr_arity(Node), + true = is_integer(A), + ok; +validate_special_type(named_fun_expr,Node) -> + A = erl_syntax:named_fun_expr_arity(Node), + true = is_integer(A), + ok; +validate_special_type(tuple,Node) -> + Size = erl_syntax:tuple_size(Node), + true = is_integer(Size), + ok; +validate_special_type(float,Node) -> + Str = erl_syntax:float_literal(Node), + Val = list_to_float(Str), + Val = erl_syntax:float_value(Node), + false = erl_syntax:is_proper_list(Node), + false = erl_syntax:is_list_skeleton(Node), + ok; +validate_special_type(integer,Node) -> + Str = erl_syntax:integer_literal(Node), + Val = list_to_integer(Str), + true = erl_syntax:is_integer(Node,Val), + Val = erl_syntax:integer_value(Node), + false = erl_syntax:is_proper_list(Node), + ok; +validate_special_type(nil,Node) -> + true = erl_syntax:is_proper_list(Node), + ok; +validate_special_type(list,Node) -> + true = erl_syntax:is_list_skeleton(Node), + _ = erl_syntax:list_tail(Node), + ErrV = erl_syntax:list_head(Node), + false = erl_syntax:is_string(Node,ErrV), + Norm = erl_syntax:normalize_list(Node), + list = erl_syntax:type(Norm), + case erl_syntax:is_proper_list(Node) of + true -> + true = erl_syntax:is_list_skeleton(Node), + Compact = erl_syntax:compact_list(Node), + list = erl_syntax:type(Compact), + [_|_] = erl_syntax:list_elements(Node), + _ = erl_syntax:list_elements(Node), + N = erl_syntax:list_length(Node), + true = N > 0, + ok; + false -> + ok + end; +validate_special_type(_,_) -> + ok. + +%%% scan_and_parse + +string_to_expr(String) -> + io:format("Str: ~p~n", [String]), + {ok, Ts, _} = erl_scan:string(String++"."), + {ok,[Expr]} = erl_parse:parse_exprs(Ts), + Expr. + p_run(Test, List) -> N = erlang:system_info(schedulers), @@ -138,4 +426,3 @@ p_run_loop(Test, List, N, Refs0, Errors0) -> Refs = Refs0 -- [Ref], p_run_loop(Test, List, N, Refs, Errors) end. - diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/m1.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/m1.erl new file mode 100644 index 0000000000..d0d1911199 --- /dev/null +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/m1.erl @@ -0,0 +1,22 @@ +%% +%% File: m1.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-10-24 +%% + +-module(m1). + +-export([foo/0,bar/1,baz/2]). + +foo() -> + [m2:foo(), + m2:bar()]. + +bar(A) -> + [m2:foo(A), + m2:bar(A), + m2:record_update(3,m2:record())]. + +baz(A,B) -> + [m2:foo(A,B), + m2:bar(A,B)]. diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/m2.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/m2.erl new file mode 100644 index 0000000000..781139317d --- /dev/null +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/m2.erl @@ -0,0 +1,26 @@ +%% +%% File: m2.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-10-24 +%% + +-module(m2). + + +-export([foo/0,foo/1,foo/2, + bar/0,bar/1,bar/2, + record_update/2, record/0]). + +foo() -> ok. +foo(A) -> [item,A]. +foo(A,B) -> A + B. + +bar() -> true. +bar(A) -> {element,A}. +bar(A,B) -> A*B. + +-record(rec, {a,b}). + +record() -> #rec{a=3,b=0}. +record_update(V,#rec{a=V0}=R) -> + R#rec{a=V0+V,b=V0}. diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl new file mode 100644 index 0000000000..07c419b4b7 --- /dev/null +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl @@ -0,0 +1,540 @@ +-module(syntax_tools_SUITE_test_module). + +-export([foo1/1,foo2/3,start_child/2]). + +-export([len/1,equal/2,concat/2,chr/2,rchr/2,str/2,rstr/2, + span/2,cspan/2,substr/2,substr/3,tokens/2,chars/2,chars/3]). +-export([copies/2,words/1,words/2,strip/1,strip/2,strip/3, + sub_word/2,sub_word/3,left/2,left/3,right/2,right/3, + sub_string/2,sub_string/3,centre/2,centre/3, join/2]). +-export([to_upper/1, to_lower/1]). + +-import(lists,[reverse/1,member/2]). + + +%% @type some_type() = map() +%% @type some_other_type() = {a, #{ list() => term()}} + +-type some_type() :: map(). +-type some_other_type() :: {'a', #{ list() => term()} }. + +-spec foo1(Map :: #{ 'a' => integer(), 'b' => term()}) -> term(). + +%% @doc Gets value from map. + +foo1(#{ a:= 1, b := V}) -> V. + +%% @spec foo2(some_type(), Type2 :: some_other_type(), map()) -> Value +%% @doc Gets value from map. + +-spec foo2( + Type1 :: some_type(), + Type2 :: some_other_type(), + Map :: #{ get => 'value', 'value' => binary()}) -> binary(). + +foo2(Type1, {a,#{ "a" := _}}, #{get := value, value := B}) when is_map(Type1) -> B. + +%% from supervisor 18.0 + +-type child() :: 'undefined' | pid(). +-type child_id() :: term(). +-type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. +-type modules() :: [module()] | 'dynamic'. +-type restart() :: 'permanent' | 'transient' | 'temporary'. +-type shutdown() :: 'brutal_kill' | timeout(). +-type worker() :: 'worker' | 'supervisor'. +-type sup_ref() :: (Name :: atom()) + | {Name :: atom(), Node :: node()} + | {'global', Name :: atom()} + | {'via', Module :: module(), Name :: any()} + | pid(). +-type child_spec() :: #{name => child_id(), % mandatory + start => mfargs(), % mandatory + restart => restart(), % optional + shutdown => shutdown(), % optional + type => worker(), % optional + modules => modules()} % optional + | {Id :: child_id(), + StartFunc :: mfargs(), + Restart :: restart(), + Shutdown :: shutdown(), + Type :: worker(), + Modules :: modules()}. + +-type startchild_err() :: 'already_present' + | {'already_started', Child :: child()} | term(). +-type startchild_ret() :: {'ok', Child :: child()} + | {'ok', Child :: child(), Info :: term()} + | {'error', startchild_err()}. + + +-spec start_child(SupRef, ChildSpec) -> startchild_ret() when + SupRef :: sup_ref(), + ChildSpec :: child_spec() | (List :: [term()]). +start_child(Supervisor, ChildSpec) -> + {Supervisor,ChildSpec}. + + +%% From string.erl +%% Robert's bit + +%% len(String) +%% Return the length of a string. + +-spec len(String) -> Length when + String :: string(), + Length :: non_neg_integer(). + +len(S) -> length(S). + +%% equal(String1, String2) +%% Test if 2 strings are equal. + +-spec equal(String1, String2) -> boolean() when + String1 :: string(), + String2 :: string(). + +equal(S, S) -> true; +equal(_, _) -> false. + +%% concat(String1, String2) +%% Concatenate 2 strings. + +-spec concat(String1, String2) -> String3 when + String1 :: string(), + String2 :: string(), + String3 :: string(). + +concat(S1, S2) -> S1 ++ S2. + +%% chr(String, Char) +%% rchr(String, Char) +%% Return the first/last index of the character in a string. + +-spec chr(String, Character) -> Index when + String :: string(), + Character :: char(), + Index :: non_neg_integer(). + +chr(S, C) when is_integer(C) -> chr(S, C, 1). + +chr([C|_Cs], C, I) -> I; +chr([_|Cs], C, I) -> chr(Cs, C, I+1); +chr([], _C, _I) -> 0. + +-spec rchr(String, Character) -> Index when + String :: string(), + Character :: char(), + Index :: non_neg_integer(). + +rchr(S, C) when is_integer(C) -> rchr(S, C, 1, 0). + +rchr([C|Cs], C, I, _L) -> %Found one, now find next! + rchr(Cs, C, I+1, I); +rchr([_|Cs], C, I, L) -> + rchr(Cs, C, I+1, L); +rchr([], _C, _I, L) -> L. + +%% str(String, SubString) +%% rstr(String, SubString) +%% index(String, SubString) +%% Return the first/last index of the sub-string in a string. +%% index/2 is kept for backwards compatibility. + +-spec str(String, SubString) -> Index when + String :: string(), + SubString :: string(), + Index :: non_neg_integer(). + +str(S, Sub) when is_list(Sub) -> str(S, Sub, 1). + +str([C|S], [C|Sub], I) -> + case prefix(Sub, S) of + true -> I; + false -> str(S, [C|Sub], I+1) + end; +str([_|S], Sub, I) -> str(S, Sub, I+1); +str([], _Sub, _I) -> 0. + +-spec rstr(String, SubString) -> Index when + String :: string(), + SubString :: string(), + Index :: non_neg_integer(). + +rstr(S, Sub) when is_list(Sub) -> rstr(S, Sub, 1, 0). + +rstr([C|S], [C|Sub], I, L) -> + case prefix(Sub, S) of + true -> rstr(S, [C|Sub], I+1, I); + false -> rstr(S, [C|Sub], I+1, L) + end; +rstr([_|S], Sub, I, L) -> rstr(S, Sub, I+1, L); +rstr([], _Sub, _I, L) -> L. + +prefix([C|Pre], [C|String]) -> prefix(Pre, String); +prefix([], String) when is_list(String) -> true; +prefix(Pre, String) when is_list(Pre), is_list(String) -> false. + +%% span(String, Chars) -> Length. +%% cspan(String, Chars) -> Length. + +-spec span(String, Chars) -> Length when + String :: string(), + Chars :: string(), + Length :: non_neg_integer(). + +span(S, Cs) when is_list(Cs) -> span(S, Cs, 0). + +span([C|S], Cs, I) -> + case member(C, Cs) of + true -> span(S, Cs, I+1); + false -> I + end; +span([], _Cs, I) -> I. + +-spec cspan(String, Chars) -> Length when + String :: string(), + Chars :: string(), + Length :: non_neg_integer(). + +cspan(S, Cs) when is_list(Cs) -> cspan(S, Cs, 0). + +cspan([C|S], Cs, I) -> + case member(C, Cs) of + true -> I; + false -> cspan(S, Cs, I+1) + end; +cspan([], _Cs, I) -> I. + +%% substr(String, Start) +%% substr(String, Start, Length) +%% Extract a sub-string from String. + +-spec substr(String, Start) -> SubString when + String :: string(), + SubString :: string(), + Start :: pos_integer(). + +substr(String, 1) when is_list(String) -> + String; +substr(String, S) when is_integer(S), S > 1 -> + substr2(String, S). + +-spec substr(String, Start, Length) -> SubString when + String :: string(), + SubString :: string(), + Start :: pos_integer(), + Length :: non_neg_integer(). + +substr(String, S, L) when is_integer(S), S >= 1, is_integer(L), L >= 0 -> + substr1(substr2(String, S), L). + +substr1([C|String], L) when L > 0 -> [C|substr1(String, L-1)]; +substr1(String, _L) when is_list(String) -> []. %Be nice! + +substr2(String, 1) when is_list(String) -> String; +substr2([_|String], S) -> substr2(String, S-1). + +%% tokens(String, Seperators). +%% Return a list of tokens seperated by characters in Seperators. + +-spec tokens(String, SeparatorList) -> Tokens when + String :: string(), + SeparatorList :: string(), + Tokens :: [Token :: nonempty_string()]. + +tokens(S, Seps) -> + tokens1(S, Seps, []). + +tokens1([C|S], Seps, Toks) -> + case member(C, Seps) of + true -> tokens1(S, Seps, Toks); + false -> tokens2(S, Seps, Toks, [C]) + end; +tokens1([], _Seps, Toks) -> + reverse(Toks). + +tokens2([C|S], Seps, Toks, Cs) -> + case member(C, Seps) of + true -> tokens1(S, Seps, [reverse(Cs)|Toks]); + false -> tokens2(S, Seps, Toks, [C|Cs]) + end; +tokens2([], _Seps, Toks, Cs) -> + reverse([reverse(Cs)|Toks]). + +-spec chars(Character, Number) -> String when + Character :: char(), + Number :: non_neg_integer(), + String :: string(). + +chars(C, N) -> chars(C, N, []). + +-spec chars(Character, Number, Tail) -> String when + Character :: char(), + Number :: non_neg_integer(), + Tail :: string(), + String :: string(). + +chars(C, N, Tail) when N > 0 -> + chars(C, N-1, [C|Tail]); +chars(C, 0, Tail) when is_integer(C) -> + Tail. + +%% Torbjörn's bit. + +%%% COPIES %%% + +-spec copies(String, Number) -> Copies when + String :: string(), + Copies :: string(), + Number :: non_neg_integer(). + +copies(CharList, Num) when is_list(CharList), is_integer(Num), Num >= 0 -> + copies(CharList, Num, []). + +copies(_CharList, 0, R) -> + R; +copies(CharList, Num, R) -> + copies(CharList, Num-1, CharList++R). + +%%% WORDS %%% + +-spec words(String) -> Count when + String :: string(), + Count :: pos_integer(). + +words(String) -> words(String, $\s). + +-spec words(String, Character) -> Count when + String :: string(), + Character :: char(), + Count :: pos_integer(). + +words(String, Char) when is_integer(Char) -> + w_count(strip(String, both, Char), Char, 0). + +w_count([], _, Num) -> Num+1; +w_count([H|T], H, Num) -> w_count(strip(T, left, H), H, Num+1); +w_count([_H|T], Char, Num) -> w_count(T, Char, Num). + +%%% SUB_WORDS %%% + +-spec sub_word(String, Number) -> Word when + String :: string(), + Word :: string(), + Number :: integer(). + +sub_word(String, Index) -> sub_word(String, Index, $\s). + +-spec sub_word(String, Number, Character) -> Word when + String :: string(), + Word :: string(), + Number :: integer(), + Character :: char(). + +sub_word(String, Index, Char) when is_integer(Index), is_integer(Char) -> + case words(String, Char) of + Num when Num < Index -> + []; + _Num -> + s_word(strip(String, left, Char), Index, Char, 1, []) + end. + +s_word([], _, _, _,Res) -> reverse(Res); +s_word([Char|_],Index,Char,Index,Res) -> reverse(Res); +s_word([H|T],Index,Char,Index,Res) -> s_word(T,Index,Char,Index,[H|Res]); +s_word([Char|T],Stop,Char,Index,Res) when Index < Stop -> + s_word(strip(T,left,Char),Stop,Char,Index+1,Res); +s_word([_|T],Stop,Char,Index,Res) when Index < Stop -> + s_word(T,Stop,Char,Index,Res). + +%%% STRIP %%% + +-spec strip(string()) -> string(). + +strip(String) -> strip(String, both). + +-spec strip(String, Direction) -> Stripped when + String :: string(), + Stripped :: string(), + Direction :: left | right | both. + +strip(String, left) -> strip_left(String, $\s); +strip(String, right) -> strip_right(String, $\s); +strip(String, both) -> + strip_right(strip_left(String, $\s), $\s). + +-spec strip(String, Direction, Character) -> Stripped when + String :: string(), + Stripped :: string(), + Direction :: left | right | both, + Character :: char(). + +strip(String, right, Char) -> strip_right(String, Char); +strip(String, left, Char) -> strip_left(String, Char); +strip(String, both, Char) -> + strip_right(strip_left(String, Char), Char). + +strip_left([Sc|S], Sc) -> + strip_left(S, Sc); +strip_left([_|_]=S, Sc) when is_integer(Sc) -> S; +strip_left([], Sc) when is_integer(Sc) -> []. + +strip_right([Sc|S], Sc) -> + case strip_right(S, Sc) of + [] -> []; + T -> [Sc|T] + end; +strip_right([C|S], Sc) -> + [C|strip_right(S, Sc)]; +strip_right([], Sc) when is_integer(Sc) -> + []. + +%%% LEFT %%% + +-spec left(String, Number) -> Left when + String :: string(), + Left :: string(), + Number :: non_neg_integer(). + +left(String, Len) when is_integer(Len) -> left(String, Len, $\s). + +-spec left(String, Number, Character) -> Left when + String :: string(), + Left :: string(), + Number :: non_neg_integer(), + Character :: char(). + +left(String, Len, Char) when is_integer(Char) -> + Slen = length(String), + if + Slen > Len -> substr(String, 1, Len); + Slen < Len -> l_pad(String, Len-Slen, Char); + Slen =:= Len -> String + end. + +l_pad(String, Num, Char) -> String ++ chars(Char, Num). + +%%% RIGHT %%% + +-spec right(String, Number) -> Right when + String :: string(), + Right :: string(), + Number :: non_neg_integer(). + +right(String, Len) when is_integer(Len) -> right(String, Len, $\s). + +-spec right(String, Number, Character) -> Right when + String :: string(), + Right :: string(), + Number :: non_neg_integer(), + Character :: char(). + +right(String, Len, Char) when is_integer(Char) -> + Slen = length(String), + if + Slen > Len -> substr(String, Slen-Len+1); + Slen < Len -> r_pad(String, Len-Slen, Char); + Slen =:= Len -> String + end. + +r_pad(String, Num, Char) -> chars(Char, Num, String). + +%%% CENTRE %%% + +-spec centre(String, Number) -> Centered when + String :: string(), + Centered :: string(), + Number :: non_neg_integer(). + +centre(String, Len) when is_integer(Len) -> centre(String, Len, $\s). + +-spec centre(String, Number, Character) -> Centered when + String :: string(), + Centered :: string(), + Number :: non_neg_integer(), + Character :: char(). + +centre(String, 0, Char) when is_list(String), is_integer(Char) -> + []; % Strange cases to centre string +centre(String, Len, Char) when is_integer(Char) -> + Slen = length(String), + if + Slen > Len -> substr(String, (Slen-Len) div 2 + 1, Len); + Slen < Len -> + N = (Len-Slen) div 2, + r_pad(l_pad(String, Len-(Slen+N), Char), N, Char); + Slen =:= Len -> String + end. + +%%% SUB_STRING %%% + +-spec sub_string(String, Start) -> SubString when + String :: string(), + SubString :: string(), + Start :: pos_integer(). + +sub_string(String, Start) -> substr(String, Start). + +-spec sub_string(String, Start, Stop) -> SubString when + String :: string(), + SubString :: string(), + Start :: pos_integer(), + Stop :: pos_integer(). + +sub_string(String, Start, Stop) -> substr(String, Start, Stop - Start + 1). + +%% ISO/IEC 8859-1 (latin1) letters are converted, others are ignored +%% + +to_lower_char(C) when is_integer(C), $A =< C, C =< $Z -> + C + 32; +to_lower_char(C) when is_integer(C), 16#C0 =< C, C =< 16#D6 -> + C + 32; +to_lower_char(C) when is_integer(C), 16#D8 =< C, C =< 16#DE -> + C + 32; +to_lower_char(C) -> + C. + +to_upper_char(C) when is_integer(C), $a =< C, C =< $z -> + C - 32; +to_upper_char(C) when is_integer(C), 16#E0 =< C, C =< 16#F6 -> + C - 32; +to_upper_char(C) when is_integer(C), 16#F8 =< C, C =< 16#FE -> + C - 32; +to_upper_char(C) -> + C. + +-spec to_lower(String) -> Result when + String :: io_lib:latin1_string(), + Result :: io_lib:latin1_string() + ; (Char) -> CharResult when + Char :: char(), + CharResult :: char(). + +to_lower(S) when is_list(S) -> + [to_lower_char(C) || C <- S]; +to_lower(C) when is_integer(C) -> + to_lower_char(C). + +-spec to_upper(String) -> Result when + String :: io_lib:latin1_string(), + Result :: io_lib:latin1_string() + ; (Char) -> CharResult when + Char :: char(), + CharResult :: char(). + +to_upper(S) when is_list(S) -> + [to_upper_char(C) || C <- S]; +to_upper(C) when is_integer(C) -> + to_upper_char(C). + +-spec join(StringList, Separator) -> String when + StringList :: [string()], + Separator :: string(), + String :: string(). + +join([], Sep) when is_list(Sep) -> + []; +join([H|T], Sep) -> + H ++ lists:append([Sep ++ X || X <- T]). diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_test.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_test.erl new file mode 100644 index 0000000000..dd3f88d7a8 --- /dev/null +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_test.erl @@ -0,0 +1,115 @@ +%% +%% File: syntax_tools_test.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-10-23 +%% + +-module(syntax_tools_test). + +-export([foo1/0,foo2/2,foo3/0,foo4/3,foo5/1]). + +-include_lib("kernel/include/file.hrl"). +-record(state, { a, b, c, d}). +-attribute([foo/0]). + +-define(attrib, some_attrib). + +-?attrib([foo2/2]). + +-define(macro_simple1, ok). +-define(MACRO_SIMPLE2, (other)). +-define(macro_simple3, ?MODULE). +-define(macro_simple4, [?macro_simple3,?MODULE,?MACRO_SIMPLE2]). +-define(macro_simple5, (process_info)). +-define(macro_string, "hello world"). +-define(macro_argument1(X), (X + 3)). +-define(macro_argument2(X,Y), (X + 3 * Y)). +-define(macro_block(X), begin X end). +-define(macro_if(X1,X2), if X1 -> X2; true -> none end). + + +-ifdef(macro_def1). +-define(macro_cond1, yep). +-else. +-define(macro_cond1, nope). +-endif. +-ifndef(macro_def2). +-define(macro_cond2, nope). +-else. +-define(macro_cond2, yep). +-endif. +-undef(macro_def1). +-undef(macro_def2). + +%% basic test +foo1() -> + ok. + +%% macro test +foo2(A,B) -> + % string combining ? + [?macro_string, ?macro_string + ?macro_string, + "hello world " + "more hello", + [?macro_simple1, + ?MACRO_SIMPLE2, + ?macro_simple3, + ?macro_simple4, + ?macro_simple5, + ?macro_string, + ?macro_cond1, + ?macro_cond2, + ?macro_block(A), + ?macro_if(A,B), + ?macro_argument1(A), + ?macro_argument1(begin A end), + ?macro_block(<<"hello">>), + ?macro_block("hello"), + ?macro_block([$h,$e,$l,$l,$0]), + ?macro_argument1(id(<<"hello">>)), + ?macro_argument1(if A -> B; true -> 3.14 end), + ?macro_argument1(case A of ok -> B; C -> C end), + ?macro_argument1(receive M -> M after 100 -> 3 end), + ?macro_argument1(try foo5(A) catch C:?macro_simple5 -> {C,B} end), + ?macro_argument2(A,B)], + A,B,ok]. + +id(I) -> I. +%% basic terms + +foo3() -> + [atom, + 'some other atom', + {tuple,1,2,3}, + 1,2,3,3333, + 3,3333,2,1, + [$a,$b,$c], + "hello world", + <<"hello world">>, + <<1,2,3,4,5:6>>, + 3.1415, + 1.03e33]. + +%% application and records + +foo4(A,B,#state{c = C}=S) -> + Ls = foo3(), + S1 = #state{ a = 1, b = 2 }, + [foo2(A,Ls),B,C, + B(3,C), + erlang:process_info(self()), + erlang:?macro_simple5(self()), + A:?MACRO_SIMPLE2(), + A:?macro_simple1(), + A:process_info(self()), + A:B(3), + S#state{ a = 2, b = B, d = S1 }]. + +foo5(A) -> + try foo2(A,A) of + R -> R + catch + error:?macro_simple5 -> + nope + end. diff --git a/lib/test_server/src/Makefile b/lib/test_server/src/Makefile index ab4dd4d95d..35bbad3c22 100644 --- a/lib/test_server/src/Makefile +++ b/lib/test_server/src/Makefile @@ -124,7 +124,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt $(INSTALL_DIR) "$(RELSYSDIR)/src" $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)/src" - $(INSTALL_DATA) $(INTERNAL_HRL_FILES) "$(RELSYSDIR)/src" + $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(TS_HRL_FILES) "$(RELSYSDIR)/src" $(INSTALL_DIR) "$(RELSYSDIR)/include" $(INSTALL_DATA) $(HRL_FILES) "$(RELSYSDIR)/include" $(INSTALL_DIR) "$(RELSYSDIR)/ebin" diff --git a/lib/test_server/src/configure.in b/lib/test_server/src/configure.in index cd723bcd4d..8398825d95 100644 --- a/lib/test_server/src/configure.in +++ b/lib/test_server/src/configure.in @@ -357,7 +357,23 @@ AC_CHECK_FUNCS(usleep) # First check if the library is available, then if we can choose between # two versions of gethostbyname AC_HAVE_LIBRARY(resolv) -AC_CHECK_LIB(resolv, res_gethostbyname,[DEFS="$DEFS -DHAVE_RES_GETHOSTBYNAME=1"]) +AC_CHECK_LIB(resolv, res_gethostbyname,[AC_DEFINE(HAVE_RES_GETHOSTBYNAME,1)]) + +#-------------------------------------------------------------------- +# Check for isfinite +#-------------------------------------------------------------------- + +AC_MSG_CHECKING([for isfinite]) +AC_TRY_LINK([#include <math.h>], + [isfinite(0);], have_isfinite=yes, have_isfinite=no) + +if test $have_isfinite = yes; then + AC_DEFINE(HAVE_ISFINITE,1) + AC_MSG_RESULT(yes) +else + AC_DEFINE(HAVE_FINITE,1) + AC_MSG_RESULT(no) +fi #-------------------------------------------------------------------- # Emulator compatible flags (for drivers) diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml index 1ba2514977..faee5efd43 100644 --- a/lib/tools/doc/src/notes.xml +++ b/lib/tools/doc/src/notes.xml @@ -30,6 +30,21 @@ </header> <p>This document describes the changes made to the Tools application.</p> +<section><title>Tools 2.7</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add log2 histogram to lcnt for lock wait time</p> + <p> + Own Id: OTP-12059</p> + </item> + </list> + </section> + +</section> + <section><title>Tools 2.6.15</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index 7379215d68..78929ac510 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -31,6 +31,7 @@ ("Module" "module" erlang-skel-module) ("Author" "author" erlang-skel-author) ("Function" "function" erlang-skel-function) + ("Spec" "spec" erlang-skel-spec) () ("Small Header" "small-header" erlang-skel-small-header erlang-skel-header) @@ -54,6 +55,8 @@ erlang-skel-gen-event erlang-skel-header) ("gen_fsm" "gen-fsm" erlang-skel-gen-fsm erlang-skel-header) + ("wx_object" "wx-object" + erlang-skel-wx-object erlang-skel-header) ("Library module" "gen-lib" erlang-skel-lib erlang-skel-header) ("Corba callback" "gen-corba-cb" @@ -147,6 +150,10 @@ Please see the function `tempo-define-template'.") "*The template of a function skeleton. Please see the function `tempo-define-template'.") +(defvar erlang-skel-spec + '("-spec " (erlang-skel-get-function-name) "(" (erlang-skel-get-function-args) ") -> undefined.") + "*The template of a -spec for the function following point. +Please see the function `tempo-define-template'.") ;; Attribute templates @@ -577,7 +584,7 @@ Please see the function `tempo-define-template'.") "-record(state, {})." n n (erlang-skel-double-separator-start 3) - "%%% gen_event callbacks" n + "%%% API" n (erlang-skel-double-separator-end 3) n (erlang-skel-separator-start 2) "%% @doc" n @@ -851,6 +858,137 @@ Please see the function `tempo-define-template'.") "*The template of a gen_fsm. Please see the function `tempo-define-template'.") +(defvar erlang-skel-wx-object + '((erlang-skel-include erlang-skel-large-header) + "-behaviour(wx_object)." n n + + "-include_lib(\"wx/include/wx.hrl\")." n n + + "%% API" n + "-export([start_link/0])." n n + + "%% wx_object callbacks" n + "-export([init/1, handle_call/3, handle_cast/2, " + "handle_info/2," n> + "handle_event/2, terminate/2, code_change/3])." n n + + "-record(state, {})." n n + + (erlang-skel-double-separator-start 3) + "%%% API" n + (erlang-skel-double-separator-end 3) n + (erlang-skel-separator-start 2) + "%% @doc" n + "%% Starts the server" n + "%%" n + "%% @spec start_link() -> wxWindow()" n + (erlang-skel-separator-end 2) + "start_link() ->" n> + "wx_object:start_link(?MODULE, [], [])." n + n + (erlang-skel-double-separator-start 3) + "%%% wx_object callbacks" n + (erlang-skel-double-separator-end 3) + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Initializes the server" n + "%%" n + "%% @spec init(Args) -> {wxWindow(), State} |" n + "%% {wxWindow(), State, Timeout} |" n + "%% ignore |" n + "%% {stop, Reason}" n + (erlang-skel-separator-end 2) + "init([]) ->" n> + "wx:new()," n> + "Frame = wxFrame:new()," n> + "{Frame, #state{}}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Handling events" n + "%%" n + "%% @spec handle_event(wx{}, State) ->" n + "%% {noreply, State} |" n + "%% {noreply, State, Timeout} |" n + "%% {stop, Reason, State}" n + (erlang-skel-separator-end 2) + "handle_event(#wx{}, State) ->" n> + "{noreply, State}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Handling call messages" n + "%%" n + "%% @spec handle_call(Request, From, State) ->" n + "%% {reply, Reply, State} |" n + "%% {reply, Reply, State, Timeout} |" n + "%% {noreply, State} |" n + "%% {noreply, State, Timeout} |" n + "%% {stop, Reason, Reply, State} |" n + "%% {stop, Reason, State}" n + (erlang-skel-separator-end 2) + "handle_call(_Request, _From, State) ->" n> + "Reply = ok," n> + "{reply, Reply, State}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Handling cast messages" n + "%%" n + "%% @spec handle_cast(Msg, State) -> {noreply, State} |" n + "%% {noreply, State, Timeout} |" n + "%% {stop, Reason, State}" n + (erlang-skel-separator-end 2) + "handle_cast(_Msg, State) ->" n> + "{noreply, State}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Handling all non call/cast messages" n + "%%" n + "%% @spec handle_info(Info, State) -> {noreply, State} |" n + "%% {noreply, State, Timeout} |" n + "%% {stop, Reason, State}" n + (erlang-skel-separator-end 2) + "handle_info(_Info, State) ->" n> + "{noreply, State}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% This function is called by a wx_object when it is about to" n + "%% terminate. It should be the opposite of Module:init/1 and do any" n + "%% necessary cleaning up. When it returns, the wx_object terminates" n + "%% with Reason. The return value is ignored." n + "%%" n + "%% @spec terminate(Reason, State) -> void()" n + (erlang-skel-separator-end 2) + "terminate(_Reason, _State) ->" n> + "ok." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Convert process state when code is changed" n + "%%" n + "%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}" n + (erlang-skel-separator-end 2) + "code_change(_OldVsn, State, _Extra) ->" n> + "{ok, State}." n + n + (erlang-skel-double-separator-start 3) + "%%% Internal functions" n + (erlang-skel-double-separator-end 3) + ) + "*The template of a generic server. +Please see the function `tempo-define-template'.") + (defvar erlang-skel-lib '((erlang-skel-include erlang-skel-large-header) @@ -1546,6 +1684,16 @@ The first character of DD is space if the value is less than 10." (substring date 4 7) (substring date -4)))) +(defun erlang-skel-get-function-name () + (save-excursion + (erlang-beginning-of-function -1) + (erlang-get-function-name))) + +(defun erlang-skel-get-function-args () + (save-excursion + (erlang-beginning-of-function -1) + (erlang-get-function-arguments))) + ;; Local variables: ;; coding: iso-8859-1 ;; End: diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index 4e3c49c717..c56759ebb9 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -7,7 +7,7 @@ ;; %CopyrightBegin% ;; -;; Copyright Ericsson AB 1996-2013. All Rights Reserved. +;; Copyright Ericsson AB 1996-2014. All Rights Reserved. ;; ;; The contents of this file are subject to the Erlang Public License, ;; Version 1.1, (the "License"); you may not use this file except in @@ -886,6 +886,7 @@ resulting regexp is surrounded by \\_< and \\_>." "flush_monitor_message" "format_cpu_topology" "fun_info" + "fun_info_mfa" "fun_to_list" "function_exported" "garbage_collect_message_area" diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk index 54dc4ec91d..3acb8d38e2 100644 --- a/lib/tools/vsn.mk +++ b/lib/tools/vsn.mk @@ -1 +1 @@ -TOOLS_VSN = 2.6.15 +TOOLS_VSN = 2.7 diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml index 63eb047caa..5a9c53e3b6 100644 --- a/lib/wx/doc/src/notes.xml +++ b/lib/wx/doc/src/notes.xml @@ -31,6 +31,23 @@ <p>This document describes the changes made to the wxErlang application.</p> +<section><title>Wx 1.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Implement --enable-sanitizers[=sanitizers]. Similar to + debugging with Valgrind, it's very useful to enable + -fsanitize= switches to catch bugs at runtime.</p> + <p> + Own Id: OTP-12153</p> + </item> + </list> + </section> + +</section> + <section><title>Wx 1.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/wx/examples/demo/ex_graphicsContext.erl b/lib/wx/examples/demo/ex_graphicsContext.erl index 59bfe7ff64..9047f1d135 100644 --- a/lib/wx/examples/demo/ex_graphicsContext.erl +++ b/lib/wx/examples/demo/ex_graphicsContext.erl @@ -54,7 +54,7 @@ do_init(Config) -> %% Setup sizers MainSizer = wxBoxSizer:new(?wxVERTICAL), Sizer = wxStaticBoxSizer:new(?wxVERTICAL, Panel, - [{label, "wxGrapicsContext"}]), + [{label, "wxGraphicsContext"}]), Win = wxPanel:new(Panel, []), Pen = ?wxBLACK_PEN, diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk index ee3d247553..24e8c2ed11 100644 --- a/lib/wx/vsn.mk +++ b/lib/wx/vsn.mk @@ -1 +1 @@ -WX_VSN = 1.3 +WX_VSN = 1.3.1 diff --git a/otp_versions.table b/otp_versions.table index e628c65444..4da3e13559 100644 --- a/otp_versions.table +++ b/otp_versions.table @@ -1,3 +1,7 @@ +OTP-17.3.3 : ssh-3.0.8 # asn1-3.0.2 common_test-1.8.2 compiler-5.0.2 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.4.1 debugger-4.0.1 dialyzer-2.7.2 diameter-1.7.1 edoc-0.7.15 eldap-1.0.4 erl_docgen-0.3.6 erl_interface-3.7.19 erts-6.2 et-1.5 eunit-2.2.8 gs-1.5.16 hipe-3.11.1 ic-4.3.6 inets-5.10.3 jinterface-1.5.11 kernel-3.0.3 megaco-3.17.2 mnesia-4.12.3 observer-2.0.2 odbc-2.10.21 orber-3.7.1 os_mon-2.3 ose-1.0.2 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22.1 reltool-0.6.6 runtime_tools-1.8.14 sasl-2.4.1 snmp-5.1 ssl-5.3.7 stdlib-2.2 syntax_tools-1.6.16 test_server-3.7.1 tools-2.7 typer-0.9.8 webtool-0.8.10 wx-1.3.1 xmerl-1.3.7 : +OTP-17.3.2 : ssh-3.0.7 ssl-5.3.7 # asn1-3.0.2 common_test-1.8.2 compiler-5.0.2 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.4.1 debugger-4.0.1 dialyzer-2.7.2 diameter-1.7.1 edoc-0.7.15 eldap-1.0.4 erl_docgen-0.3.6 erl_interface-3.7.19 erts-6.2 et-1.5 eunit-2.2.8 gs-1.5.16 hipe-3.11.1 ic-4.3.6 inets-5.10.3 jinterface-1.5.11 kernel-3.0.3 megaco-3.17.2 mnesia-4.12.3 observer-2.0.2 odbc-2.10.21 orber-3.7.1 os_mon-2.3 ose-1.0.2 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22.1 reltool-0.6.6 runtime_tools-1.8.14 sasl-2.4.1 snmp-5.1 stdlib-2.2 syntax_tools-1.6.16 test_server-3.7.1 tools-2.7 typer-0.9.8 webtool-0.8.10 wx-1.3.1 xmerl-1.3.7 : +OTP-17.3.1 : eldap-1.0.4 erl_interface-3.7.19 jinterface-1.5.11 orber-3.7.1 ose-1.0.2 ssh-3.0.6 # asn1-3.0.2 common_test-1.8.2 compiler-5.0.2 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.4.1 debugger-4.0.1 dialyzer-2.7.2 diameter-1.7.1 edoc-0.7.15 erl_docgen-0.3.6 erts-6.2 et-1.5 eunit-2.2.8 gs-1.5.16 hipe-3.11.1 ic-4.3.6 inets-5.10.3 kernel-3.0.3 megaco-3.17.2 mnesia-4.12.3 observer-2.0.2 odbc-2.10.21 os_mon-2.3 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22.1 reltool-0.6.6 runtime_tools-1.8.14 sasl-2.4.1 snmp-5.1 ssl-5.3.6 stdlib-2.2 syntax_tools-1.6.16 test_server-3.7.1 tools-2.7 typer-0.9.8 webtool-0.8.10 wx-1.3.1 xmerl-1.3.7 : +OTP-17.3 : asn1-3.0.2 common_test-1.8.2 compiler-5.0.2 crypto-3.4.1 dialyzer-2.7.2 diameter-1.7.1 edoc-0.7.15 erl_docgen-0.3.6 erl_interface-3.7.18 erts-6.2 eunit-2.2.8 hipe-3.11.1 ic-4.3.6 inets-5.10.3 jinterface-1.5.10 kernel-3.0.3 megaco-3.17.2 mnesia-4.12.3 observer-2.0.2 odbc-2.10.21 os_mon-2.3 ose-1.0.1 public_key-0.22.1 sasl-2.4.1 snmp-5.1 ssh-3.0.5 ssl-5.3.6 stdlib-2.2 tools-2.7 wx-1.3.1 # cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 debugger-4.0.1 eldap-1.0.3 et-1.5 gs-1.5.16 orber-3.7 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 reltool-0.6.6 runtime_tools-1.8.14 syntax_tools-1.6.16 test_server-3.7.1 typer-0.9.8 webtool-0.8.10 xmerl-1.3.7 : OTP-17.2.2 : mnesia-4.12.2 # asn1-3.0.1 common_test-1.8.1 compiler-5.0.1 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.4 debugger-4.0.1 dialyzer-2.7.1 diameter-1.7 edoc-0.7.14 eldap-1.0.3 erl_docgen-0.3.5 erl_interface-3.7.17 erts-6.1.2 et-1.5 eunit-2.2.7 gs-1.5.16 hipe-3.11 ic-4.3.5 inets-5.10.2 jinterface-1.5.9 kernel-3.0.2 megaco-3.17.1 observer-2.0.1 odbc-2.10.20 orber-3.7 os_mon-2.2.15 ose-1.0 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22 reltool-0.6.6 runtime_tools-1.8.14 sasl-2.4 snmp-5.0 ssh-3.0.4 ssl-5.3.5 stdlib-2.1.1 syntax_tools-1.6.16 test_server-3.7.1 tools-2.6.15 typer-0.9.8 webtool-0.8.10 wx-1.3 xmerl-1.3.7 : OTP-17.2.1 : ssh-3.0.4 # asn1-3.0.1 common_test-1.8.1 compiler-5.0.1 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.4 debugger-4.0.1 dialyzer-2.7.1 diameter-1.7 edoc-0.7.14 eldap-1.0.3 erl_docgen-0.3.5 erl_interface-3.7.17 erts-6.1.2 et-1.5 eunit-2.2.7 gs-1.5.16 hipe-3.11 ic-4.3.5 inets-5.10.2 jinterface-1.5.9 kernel-3.0.2 megaco-3.17.1 mnesia-4.12.1 observer-2.0.1 odbc-2.10.20 orber-3.7 os_mon-2.2.15 ose-1.0 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22 reltool-0.6.6 runtime_tools-1.8.14 sasl-2.4 snmp-5.0 ssl-5.3.5 stdlib-2.1.1 syntax_tools-1.6.16 test_server-3.7.1 tools-2.6.15 typer-0.9.8 webtool-0.8.10 wx-1.3 xmerl-1.3.7 : OTP-17.2 : orber-3.7 snmp-5.0 # asn1-3.0.1 common_test-1.8.1 compiler-5.0.1 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.4 debugger-4.0.1 dialyzer-2.7.1 diameter-1.7 edoc-0.7.14 eldap-1.0.3 erl_docgen-0.3.5 erl_interface-3.7.17 erts-6.1.2 et-1.5 eunit-2.2.7 gs-1.5.16 hipe-3.11 ic-4.3.5 inets-5.10.2 jinterface-1.5.9 kernel-3.0.2 megaco-3.17.1 mnesia-4.12.1 observer-2.0.1 odbc-2.10.20 os_mon-2.2.15 ose-1.0 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22 reltool-0.6.6 runtime_tools-1.8.14 sasl-2.4 ssh-3.0.3 ssl-5.3.5 stdlib-2.1.1 syntax_tools-1.6.16 test_server-3.7.1 tools-2.6.15 typer-0.9.8 webtool-0.8.10 wx-1.3 xmerl-1.3.7 : diff --git a/system/doc/efficiency_guide/processes.xml b/system/doc/efficiency_guide/processes.xml index 6f85b029eb..86951e2dcc 100644 --- a/system/doc/efficiency_guide/processes.xml +++ b/system/doc/efficiency_guide/processes.xml @@ -186,7 +186,7 @@ kilo_byte(0, Acc) -> kilo_byte(N, Acc) -> kilo_byte(N-1, [Acc|Acc]).</code> - <p><c>kilo_byte/1</c> creates a deep list. If we call + <p><c>kilo_byte/0</c> creates a deep list. If we call <c>list_to_binary/1</c>, we can convert the deep list to a binary of 1024 bytes:</p> |